| //! `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))] |
| // It's useful to refer to code that is private in doc comments. |
| #![allow(rustdoc::private_intra_doc_links)] |
| |
| pub use intern; |
| |
| pub mod attrs; |
| pub mod builtin; |
| pub mod change; |
| 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; |
| mod prettify_macro_expansion_; |
| |
| use salsa::plumbing::{AsId, FromId}; |
| use thin_vec::ThinVec; |
| use triomphe::Arc; |
| |
| use core::fmt; |
| use std::{borrow::Cow, ops}; |
| |
| use base_db::{Crate, SourceDatabase}; |
| use either::Either; |
| use mbe::MatchedArmIndex; |
| use span::{ |
| AstIdMap, Edition, ErasedFileAstId, FileAstId, NO_DOWNMAP_ERASED_FILE_AST_ID_MARKER, Span, |
| SyntaxContext, |
| }; |
| use syntax::{ |
| Parse, SyntaxError, SyntaxNode, SyntaxToken, T, TextRange, TextSize, |
| ast::{self, AstNode}, |
| }; |
| use syntax_bridge::{DocCommentDesugarMode, syntax_node_to_token_tree}; |
| |
| use crate::{ |
| attrs::AttrId, |
| builtin::{ |
| BuiltinAttrExpander, BuiltinDeriveExpander, BuiltinFnLikeExpander, EagerExpander, |
| include_input_to_file_id, pseudo_derive_attr_expansion, |
| }, |
| cfg_process::attr_macro_input_to_token_tree, |
| fixup::SyntaxFixupUndoInfo, |
| hygiene::{span_with_call_site_ctxt, span_with_def_site_ctxt, span_with_mixed_site_ctxt}, |
| proc_macro::{CustomProcMacroExpander, ProcMacroKind, ProcMacros}, |
| span_map::{ExpansionSpanMap, RealSpanMap, SpanMap}, |
| }; |
| |
| pub use crate::{ |
| files::{AstId, ErasedAstId, FileRange, InFile, InMacroFile, InRealFile}, |
| prettify_macro_expansion_::prettify_macro_expansion, |
| }; |
| |
| pub use base_db::EditionedFileId; |
| pub use mbe::{DeclarativeMacro, MacroCallStyle, MacroCallStyles, ValueResult}; |
| |
| pub use tt; |
| |
| /// This is just to ensure the types of [`MacroCallId::macro_arg_considering_derives`] |
| /// and [`MacroCallId::macro_arg`] are the same. |
| type MacroArgResult = (tt::TopSubtree, SyntaxFixupUndoInfo, Span); |
| |
| /// Total limit on the number of tokens produced by any macro invocation. |
| /// |
| /// If an invocation produces more tokens than this limit, it will not be stored in the database and |
| /// an error will be emitted. |
| /// |
| /// Actual max for `analysis-stats .` at some point: 30672. |
| const TOKEN_LIMIT: usize = 2_097_152; |
| |
| #[macro_export] |
| macro_rules! impl_intern_lookup { |
| ($id:ident, $loc:ident) => { |
| impl $crate::Intern for $loc { |
| type ID = $id; |
| fn intern(self, db: &dyn ::base_db::SourceDatabase) -> Self::ID { |
| $id::new(db, self) |
| } |
| } |
| |
| impl $crate::Lookup for $id { |
| type Data = $loc; |
| fn lookup<'db>(&self, db: &'db dyn ::base_db::SourceDatabase) -> &'db Self::Data { |
| self.loc(db) |
| } |
| } |
| }; |
| } |
| |
| // ideally these would be defined in base-db, but the orphan rule doesn't let us |
| pub trait Intern { |
| type ID; |
| fn intern(self, db: &dyn SourceDatabase) -> Self::ID; |
| } |
| |
| pub trait Lookup { |
| type Data; |
| fn lookup<'db>(&self, db: &'db dyn SourceDatabase) -> &'db Self::Data; |
| } |
| |
| impl_intern_lookup!(MacroCallId, MacroCallLoc); |
| |
| 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 |
| } |
| |
| pub fn render_to_string(&self, db: &dyn SourceDatabase) -> RenderedExpandError { |
| self.inner.0.render_to_string(db) |
| } |
| } |
| |
| #[derive(Debug, PartialEq, Eq, Clone, Hash)] |
| pub enum ExpandErrorKind { |
| /// Attribute macro expansion is disabled. |
| ProcMacroAttrExpansionDisabled, |
| MissingProcMacroExpander(Crate), |
| /// The macro for this call is disabled. |
| MacroDisabled, |
| /// The macro definition has errors. |
| MacroDefinition, |
| Mbe(mbe::ExpandErrorKind), |
| RecursionOverflow, |
| Other(Box<str>), |
| ProcMacroPanic(Box<str>), |
| } |
| |
| pub struct RenderedExpandError { |
| pub message: String, |
| pub error: bool, |
| pub kind: &'static str, |
| } |
| |
| impl fmt::Display for RenderedExpandError { |
| fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { |
| write!(f, "{}", self.message) |
| } |
| } |
| |
| impl RenderedExpandError { |
| const GENERAL_KIND: &str = "macro-error"; |
| const DISABLED: &str = "proc-macro-disabled"; |
| const ATTR_EXP_DISABLED: &str = "attribute-expansion-disabled"; |
| } |
| |
| impl ExpandErrorKind { |
| pub fn render_to_string(&self, db: &dyn SourceDatabase) -> RenderedExpandError { |
| match self { |
| ExpandErrorKind::ProcMacroAttrExpansionDisabled => RenderedExpandError { |
| message: "procedural attribute macro expansion is disabled".to_owned(), |
| error: false, |
| kind: RenderedExpandError::ATTR_EXP_DISABLED, |
| }, |
| ExpandErrorKind::MacroDisabled => RenderedExpandError { |
| message: "proc-macro is explicitly disabled".to_owned(), |
| error: false, |
| kind: RenderedExpandError::DISABLED, |
| }, |
| &ExpandErrorKind::MissingProcMacroExpander(def_crate) => { |
| match ProcMacros::get_for_crate(db, def_crate).and_then(|it| it.get_error()) { |
| Some(e) => RenderedExpandError { |
| message: e.to_string(), |
| error: e.is_hard_error(), |
| kind: RenderedExpandError::GENERAL_KIND, |
| }, |
| None => RenderedExpandError { |
| message: format!( |
| "internal error: proc-macro map is missing error entry for crate {def_crate:?}" |
| ), |
| error: true, |
| kind: RenderedExpandError::GENERAL_KIND, |
| }, |
| } |
| } |
| ExpandErrorKind::MacroDefinition => RenderedExpandError { |
| message: "macro definition has parse errors".to_owned(), |
| error: true, |
| kind: RenderedExpandError::GENERAL_KIND, |
| }, |
| ExpandErrorKind::Mbe(e) => RenderedExpandError { |
| message: e.to_string(), |
| error: true, |
| kind: RenderedExpandError::GENERAL_KIND, |
| }, |
| ExpandErrorKind::RecursionOverflow => RenderedExpandError { |
| message: "overflow expanding the original macro".to_owned(), |
| error: true, |
| kind: RenderedExpandError::GENERAL_KIND, |
| }, |
| ExpandErrorKind::Other(e) => RenderedExpandError { |
| message: (**e).to_owned(), |
| error: true, |
| kind: RenderedExpandError::GENERAL_KIND, |
| }, |
| ExpandErrorKind::ProcMacroPanic(e) => RenderedExpandError { |
| message: format!("proc-macro panicked: {e}"), |
| error: true, |
| kind: RenderedExpandError::GENERAL_KIND, |
| }, |
| } |
| } |
| } |
| |
| 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: Crate, |
| pub kind: MacroCallKind, |
| pub ctxt: SyntaxContext, |
| /// The macro recursion depth of this expansion. |
| /// |
| /// Because macro expansions can cross `DefMap` boundaries, we must track this inside the expansion |
| /// to not recurse infinitely. |
| pub macro_depth: u32, |
| } |
| |
| #[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] |
| pub struct MacroDefId { |
| pub krate: Crate, |
| 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>, MacroCallStyles), |
| BuiltIn(AstId<ast::Macro>, BuiltinFnLikeExpander), |
| BuiltInAttr(AstId<ast::Macro>, BuiltinAttrExpander), |
| BuiltInDerive(AstId<ast::Macro>, BuiltinDeriveExpander), |
| BuiltInEager(AstId<ast::Macro>, EagerExpander), |
| UnimplementedBuiltIn(AstId<ast::Macro>), |
| ProcMacro(AstId<ast::Fn>, CustomProcMacroExpander, ProcMacroKind), |
| } |
| |
| impl MacroDefKind { |
| #[inline] |
| pub fn is_declarative(&self) -> bool { |
| matches!(self, MacroDefKind::Declarative(..)) |
| } |
| |
| pub fn erased_ast_id(&self) -> ErasedAstId { |
| match *self { |
| MacroDefKind::ProcMacro(id, ..) => id.erase(), |
| MacroDefKind::BuiltIn(id, _) |
| | MacroDefKind::BuiltInAttr(id, _) |
| | MacroDefKind::BuiltInDerive(id, _) |
| | MacroDefKind::BuiltInEager(id, _) |
| | MacroDefKind::Declarative(id, ..) |
| | MacroDefKind::UnimplementedBuiltIn(id) => id.erase(), |
| } |
| } |
| } |
| |
| #[derive(Debug, Clone, PartialEq, Eq, Hash)] |
| pub struct EagerCallInfo { |
| /// The expanded argument of the eager macro. |
| arg: tt::TopSubtree, |
| /// 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<Box<EagerCallInfo>>, |
| }, |
| Derive { |
| ast_id: AstId<ast::Adt>, |
| /// Syntactical index of the invoking `#[derive]` attribute. |
| 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`. |
| attr_args: Option<Box<tt::TopSubtree>>, |
| /// This contains the list of all *active* attributes (derives and attr macros) preceding this |
| /// attribute, including this attribute. You can retrieve the [`AttrId`] of the current attribute |
| /// by calling [`invoc_attr()`] on this. |
| /// |
| /// The macro should not see the attributes here. |
| /// |
| /// [`invoc_attr()`]: AttrMacroAttrIds::invoc_attr |
| censored_attr_ids: AttrMacroAttrIds, |
| }, |
| } |
| |
| #[derive(Debug, Clone, PartialEq, Eq, Hash)] |
| pub struct AttrMacroAttrIds(AttrMacroAttrIdsRepr); |
| |
| impl AttrMacroAttrIds { |
| #[inline] |
| pub fn from_one(id: AttrId) -> Self { |
| Self(AttrMacroAttrIdsRepr::One(id)) |
| } |
| |
| #[inline] |
| pub fn from_many(ids: &[AttrId]) -> Self { |
| if let &[id] = ids { |
| Self(AttrMacroAttrIdsRepr::One(id)) |
| } else { |
| Self(AttrMacroAttrIdsRepr::ManyDerives(ids.iter().copied().collect())) |
| } |
| } |
| } |
| |
| #[derive(Debug, Clone, PartialEq, Eq, Hash)] |
| enum AttrMacroAttrIdsRepr { |
| One(AttrId), |
| ManyDerives(ThinVec<AttrId>), |
| } |
| |
| impl ops::Deref for AttrMacroAttrIds { |
| type Target = [AttrId]; |
| |
| #[inline] |
| fn deref(&self) -> &Self::Target { |
| match &self.0 { |
| AttrMacroAttrIdsRepr::One(one) => std::slice::from_ref(one), |
| AttrMacroAttrIdsRepr::ManyDerives(many) => many, |
| } |
| } |
| } |
| |
| impl AttrMacroAttrIds { |
| #[inline] |
| pub fn invoc_attr(&self) -> AttrId { |
| match &self.0 { |
| AttrMacroAttrIdsRepr::One(it) => *it, |
| AttrMacroAttrIdsRepr::ManyDerives(it) => { |
| *it.last().expect("should always have at least one `AttrId`") |
| } |
| } |
| } |
| } |
| |
| impl MacroCallKind { |
| pub(crate) fn call_style(&self) -> MacroCallStyle { |
| match self { |
| MacroCallKind::FnLike { .. } => MacroCallStyle::FnLike, |
| MacroCallKind::Derive { .. } => MacroCallStyle::Derive, |
| MacroCallKind::Attr { .. } => MacroCallStyle::Attr, |
| } |
| } |
| } |
| |
| #[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] |
| pub enum MacroKind { |
| /// `macro_rules!` or Macros 2.0 macro. |
| Declarative, |
| /// A built-in function-like macro. |
| DeclarativeBuiltIn, |
| /// A custom derive. |
| Derive, |
| /// A builtin-in derive. |
| DeriveBuiltIn, |
| /// A procedural attribute macro. |
| Attr, |
| /// A built-in attribute macro. |
| AttrBuiltIn, |
| /// A function-like procedural macro. |
| ProcMacro, |
| } |
| |
| impl MacroCallId { |
| pub fn call_node(self, db: &dyn SourceDatabase) -> InFile<SyntaxNode> { |
| self.loc(db).to_node(db) |
| } |
| pub fn expansion_level(self, db: &dyn SourceDatabase) -> u32 { |
| let mut level = 0; |
| let mut macro_file = self; |
| loop { |
| let loc = macro_file.loc(db); |
| |
| level += 1; |
| macro_file = match loc.kind.file_id() { |
| HirFileId::FileId(_) => break level, |
| HirFileId::MacroFile(it) => it, |
| }; |
| } |
| } |
| pub fn parent(self, db: &dyn SourceDatabase) -> HirFileId { |
| self.loc(db).kind.file_id() |
| } |
| |
| /// Return expansion information if it is a macro-expansion file |
| pub fn expansion_info(self, db: &dyn SourceDatabase) -> ExpansionInfo<'_> { |
| ExpansionInfo::new(db, self) |
| } |
| |
| pub fn kind(self, db: &dyn SourceDatabase) -> MacroKind { |
| match self.loc(db).def.kind { |
| MacroDefKind::Declarative(..) => MacroKind::Declarative, |
| MacroDefKind::BuiltIn(..) | MacroDefKind::BuiltInEager(..) => { |
| MacroKind::DeclarativeBuiltIn |
| } |
| MacroDefKind::BuiltInDerive(..) => MacroKind::DeriveBuiltIn, |
| MacroDefKind::ProcMacro(_, _, ProcMacroKind::CustomDerive) => MacroKind::Derive, |
| MacroDefKind::ProcMacro(_, _, ProcMacroKind::Attr) => MacroKind::Attr, |
| MacroDefKind::ProcMacro(_, _, ProcMacroKind::Bang) => MacroKind::ProcMacro, |
| MacroDefKind::BuiltInAttr(..) => MacroKind::AttrBuiltIn, |
| MacroDefKind::UnimplementedBuiltIn(..) => MacroKind::Declarative, |
| } |
| } |
| |
| pub fn is_include_macro(self, db: &dyn SourceDatabase) -> bool { |
| self.loc(db).def.is_include() |
| } |
| |
| pub fn is_include_like_macro(self, db: &dyn SourceDatabase) -> bool { |
| self.loc(db).def.is_include_like() |
| } |
| |
| pub fn is_env_or_option_env(self, db: &dyn SourceDatabase) -> bool { |
| self.loc(db).def.is_env_or_option_env() |
| } |
| |
| pub fn is_eager(self, db: &dyn SourceDatabase) -> bool { |
| let loc = self.loc(db); |
| matches!(loc.def.kind, MacroDefKind::BuiltInEager(..)) |
| } |
| |
| pub fn eager_arg(self, db: &dyn SourceDatabase) -> Option<MacroCallId> { |
| let loc = self.loc(db); |
| match &loc.kind { |
| MacroCallKind::FnLike { eager, .. } => eager.as_ref().map(|it| it.arg_id), |
| _ => None, |
| } |
| } |
| |
| pub fn is_derive_attr_pseudo_expansion(self, db: &dyn SourceDatabase) -> bool { |
| let loc = self.loc(db); |
| loc.def.is_attribute_derive() |
| } |
| } |
| |
| #[salsa::tracked] |
| impl MacroCallId { |
| /// Implementation of [`HirFileId::parse_or_expand`] for the macro case. |
| // FIXME: We should verify that the parsed node is one of the many macro node variants we expect |
| // instead of having it be untyped |
| #[salsa::tracked(returns(ref), lru = 512)] |
| pub fn parse_macro_expansion( |
| self, |
| db: &dyn SourceDatabase, |
| ) -> ExpandResult<(Parse<SyntaxNode>, ExpansionSpanMap)> { |
| let _p = tracing::info_span!("parse_macro_expansion").entered(); |
| let loc = self.loc(db); |
| let expand_to = loc.expand_to(); |
| let mbe::ValueResult { value: (tt, matched_arm), err } = self.macro_expand(db, loc); |
| |
| let (parse, mut rev_token_map) = token_tree_to_syntax_node(db, &tt, expand_to); |
| rev_token_map.matched_arm = matched_arm; |
| |
| ExpandResult { value: (parse, rev_token_map), err } |
| } |
| |
| pub fn parse_macro_expansion_error( |
| self, |
| db: &dyn SourceDatabase, |
| ) -> Option<ExpandResult<Arc<[SyntaxError]>>> { |
| let e: ExpandResult<Arc<[SyntaxError]>> = |
| self.parse_macro_expansion(db).as_ref().map(|it| Arc::from(it.0.errors())); |
| if e.value.is_empty() && e.err.is_none() { None } else { Some(e) } |
| } |
| |
| /// This resolves the [MacroCallId] to check if it is a derive macro if so get the [macro_arg] for the derive. |
| /// Other wise return the [macro_arg] for the macro_call_id. |
| /// |
| /// This is not connected to the database so it does not cache the result. However, the inner [macro_arg] query is |
| /// |
| /// [macro_arg]: Self::macro_arg |
| #[allow(deprecated)] // we are macro_arg_considering_derives |
| pub fn macro_arg_considering_derives<'db>( |
| self, |
| db: &'db dyn SourceDatabase, |
| kind: &MacroCallKind, |
| ) -> &'db MacroArgResult { |
| match kind { |
| // Get the macro arg for the derive macro |
| MacroCallKind::Derive { derive_macro_id, .. } => derive_macro_id.macro_arg(db), |
| // Normal macro arg |
| _ => self.macro_arg(db), |
| } |
| } |
| |
| /// Lowers syntactic macro call to a token tree representation. That's a firewall |
| /// query, only typing in the macro call itself changes the returned |
| /// subtree. |
| #[salsa::tracked(returns(ref))] |
| fn macro_arg(self, db: &dyn SourceDatabase) -> MacroArgResult { |
| let loc = self.loc(db); |
| |
| if let MacroCallLoc { |
| def: MacroDefId { kind: MacroDefKind::BuiltInEager(..), .. }, |
| kind: MacroCallKind::FnLike { eager: Some(eager), .. }, |
| .. |
| } = &loc |
| { |
| return (eager.arg.clone(), SyntaxFixupUndoInfo::NONE, eager.span); |
| } |
| |
| let (parse, map) = loc.kind.file_id().parse_with_map(db); |
| let root = parse.syntax_node(); |
| |
| let (is_derive, censor_item_tree_attr_ids, item_node, span) = match &loc.kind { |
| MacroCallKind::FnLike { ast_id, .. } => { |
| let node = &ast_id.to_ptr(db).to_node(&root); |
| let path_range = node |
| .path() |
| .map_or_else(|| node.syntax().text_range(), |path| path.syntax().text_range()); |
| let span = map.span_for_range(path_range); |
| |
| let dummy_tt = |kind| { |
| ( |
| tt::TopSubtree::from_token_trees( |
| tt::Delimiter { open: span, close: span, kind }, |
| tt::TokenTreesView::empty(), |
| ), |
| SyntaxFixupUndoInfo::default(), |
| span, |
| ) |
| }; |
| |
| let Some(tt) = node.token_tree() else { |
| return dummy_tt(tt::DelimiterKind::Invisible); |
| }; |
| let first = tt.left_delimiter_token().map(|it| it.kind()).unwrap_or(T!['(']); |
| let last = tt.right_delimiter_token().map(|it| it.kind()).unwrap_or(T![.]); |
| |
| let mismatched_delimiters = !matches!( |
| (first, last), |
| (T!['('], T![')']) | (T!['['], T![']']) | (T!['{'], T!['}']) |
| ); |
| if mismatched_delimiters { |
| // Don't expand malformed (unbalanced) macro invocations. This is |
| // less than ideal, but trying to expand unbalanced macro calls |
| // sometimes produces pathological, deeply nested code which breaks |
| // all kinds of things. |
| // |
| // So instead, we'll return an empty subtree here |
| cov_mark::hit!(issue9358_bad_macro_stack_overflow); |
| |
| let kind = match first { |
| _ if loc.def.is_proc_macro() => tt::DelimiterKind::Invisible, |
| T!['('] => tt::DelimiterKind::Parenthesis, |
| T!['['] => tt::DelimiterKind::Bracket, |
| T!['{'] => tt::DelimiterKind::Brace, |
| _ => tt::DelimiterKind::Invisible, |
| }; |
| return dummy_tt(kind); |
| } |
| |
| let mut tt = syntax_bridge::syntax_node_to_token_tree( |
| tt.syntax(), |
| map, |
| span, |
| if loc.def.is_proc_macro() { |
| DocCommentDesugarMode::ProcMacro |
| } else { |
| DocCommentDesugarMode::Mbe |
| }, |
| ); |
| if loc.def.is_proc_macro() { |
| // proc macros expect their inputs without parentheses, MBEs expect it with them included |
| tt.set_top_subtree_delimiter_kind(tt::DelimiterKind::Invisible); |
| } |
| return (tt, SyntaxFixupUndoInfo::NONE, span); |
| } |
| // MacroCallKind::Derive should not be here. As we are getting the argument for the derive macro |
| MacroCallKind::Derive { .. } => { |
| unreachable!("`MacroCallId::macro_arg` called with `MacroCallKind::Derive`") |
| } |
| MacroCallKind::Attr { ast_id, censored_attr_ids: attr_ids, .. } => { |
| let node = ast_id.to_ptr(db).to_node(&root); |
| let (_, attr) = |
| attr_ids.invoc_attr().find_attr_range_with_source(db, loc.krate, &node); |
| let range = attr |
| .path() |
| .map(|path| path.syntax().text_range()) |
| .unwrap_or_else(|| attr.syntax().text_range()); |
| let span = map.span_for_range(range); |
| |
| let is_derive = matches!(loc.def.kind, MacroDefKind::BuiltInAttr(_, expander) if expander.is_derive()); |
| (is_derive, &**attr_ids, node, span) |
| } |
| }; |
| |
| let (mut tt, undo_info) = attr_macro_input_to_token_tree( |
| db, |
| item_node.syntax(), |
| map, |
| span, |
| is_derive, |
| censor_item_tree_attr_ids, |
| loc.krate, |
| ); |
| |
| if loc.def.is_proc_macro() { |
| // proc macros expect their inputs without parentheses, MBEs expect it with them included |
| tt.set_top_subtree_delimiter_kind(tt::DelimiterKind::Invisible); |
| } |
| |
| (tt, undo_info, span) |
| } |
| |
| fn macro_expand<'db>( |
| self, |
| db: &'db dyn SourceDatabase, |
| loc: &MacroCallLoc, |
| ) -> ExpandResult<(Cow<'db, tt::TopSubtree>, MatchedArmIndex)> { |
| let _p = tracing::info_span!("macro_expand").entered(); |
| |
| let (ExpandResult { value: (tt, matched_arm), err }, span) = match loc.def.kind { |
| MacroDefKind::ProcMacro(..) => { |
| return self.expand_proc_macro(db).as_ref().map(|it| (Cow::Borrowed(it), None)); |
| } |
| _ => { |
| let (macro_arg, undo_info, span) = |
| self.macro_arg_considering_derives(db, &loc.kind); |
| let span = *span; |
| |
| let arg = macro_arg; |
| let res = match loc.def.kind { |
| MacroDefKind::Declarative(id, _) => { |
| id.decl_macro_expander(db, loc.def.krate).expand(db, arg, self, span) |
| } |
| MacroDefKind::BuiltIn(_, it) => { |
| it.expand(db, self, arg, span).map_err(Into::into).zip_val(None) |
| } |
| MacroDefKind::BuiltInDerive(_, it) => { |
| it.expand(db, self, arg, span).map_err(Into::into).zip_val(None) |
| } |
| MacroDefKind::UnimplementedBuiltIn(_) => { |
| expand_unimplemented_builtin_macro(span).zip_val(None) |
| } |
| MacroDefKind::BuiltInEager(_, it) => { |
| // This might look a bit odd, but we do not expand the inputs to eager macros here. |
| // Eager macros inputs are expanded, well, eagerly when we collect the macro calls. |
| // That kind of expansion uses the ast id map of an eager macros input though which goes through |
| // the HirFileId machinery. As eager macro inputs are assigned a macro file id that query |
| // will end up going through here again, whereas we want to just want to inspect the raw input. |
| // As such we just return the input subtree here. |
| let eager = match &loc.kind { |
| MacroCallKind::FnLike { eager: None, .. } => { |
| return ExpandResult::ok(Cow::Borrowed(macro_arg)).zip_val(None); |
| } |
| MacroCallKind::FnLike { eager: Some(eager), .. } => Some(&**eager), |
| _ => None, |
| }; |
| |
| let mut res = it.expand(db, self, arg, span).map_err(Into::into); |
| |
| if let Some(EagerCallInfo { error, .. }) = eager { |
| // FIXME: We should report both errors! |
| res.err = error.clone().or(res.err); |
| } |
| res.zip_val(None) |
| } |
| MacroDefKind::BuiltInAttr(_, it) => { |
| let mut res = it.expand(db, self, arg, span); |
| fixup::reverse_fixups(&mut res.value, undo_info); |
| res.zip_val(None) |
| } |
| MacroDefKind::ProcMacro(_, _, _) => unreachable!(), |
| }; |
| (res, span) |
| } |
| }; |
| |
| // Skip checking token tree limit for include! macro call |
| if !loc.def.is_include() { |
| // Set a hard limit for the expanded tt |
| if let Err(value) = check_tt_count(&tt) { |
| return value |
| .map(|()| Cow::Owned(tt::TopSubtree::empty(tt::DelimSpan::from_single(span)))) |
| .zip_val(matched_arm); |
| } |
| } |
| |
| ExpandResult { value: (Cow::Owned(tt), matched_arm), err } |
| } |
| |
| /// Special case of [`Self::macro_expand`] for procedural macros. We can't LRU |
| /// proc macros, since they are not deterministic in general, and |
| /// non-determinism breaks salsa in a very, very, very bad way. |
| /// @edwin0cheng heroically debugged this once! See #4315 for details |
| #[salsa::tracked(returns(ref))] |
| fn expand_proc_macro(self, db: &dyn SourceDatabase) -> ExpandResult<tt::TopSubtree> { |
| let loc = self.loc(db); |
| let (macro_arg, undo_info, span) = self.macro_arg_considering_derives(db, &loc.kind); |
| |
| let (ast, expander) = match loc.def.kind { |
| MacroDefKind::ProcMacro(ast, expander, _) => (ast, expander), |
| _ => unreachable!(), |
| }; |
| |
| let attr_arg = match &loc.kind { |
| MacroCallKind::Attr { attr_args: Some(attr_args), .. } => Some(&**attr_args), |
| _ => None, |
| }; |
| |
| let ExpandResult { value: mut tt, err } = { |
| let span = proc_macro_span(db, ast); |
| expander.expand( |
| db, |
| loc.def.krate, |
| loc.krate, |
| macro_arg, |
| attr_arg, |
| span_with_def_site_ctxt(db, span, self.into(), loc.def.edition), |
| span_with_call_site_ctxt(db, span, self.into(), loc.def.edition), |
| span_with_mixed_site_ctxt(db, span, self.into(), loc.def.edition), |
| ) |
| }; |
| |
| // Set a hard limit for the expanded tt |
| if let Err(value) = check_tt_count(&tt) { |
| return value.map(|()| tt::TopSubtree::empty(tt::DelimSpan::from_single(*span))); |
| } |
| |
| fixup::reverse_fixups(&mut tt, undo_info); |
| |
| ExpandResult { value: tt, err } |
| } |
| } |
| |
| impl MacroCallId { |
| /// This expands the given macro call, but with different arguments. This is |
| /// used for completion, where we want to see what 'would happen' if we insert a |
| /// token. The `token_to_map` mapped down into the expansion, with the mapped |
| /// token(s) returned with their priority. |
| pub fn expand_speculative( |
| self, |
| db: &dyn SourceDatabase, |
| speculative_args: &SyntaxNode, |
| token_to_map: SyntaxToken, |
| ) -> Option<(SyntaxNode, Vec<(SyntaxToken, u8)>)> { |
| let loc = self.loc(db); |
| let (_, _, span) = *self.macro_arg_considering_derives(db, &loc.kind); |
| |
| let span_map = RealSpanMap::absolute(span.anchor.file_id); |
| let span_map = SpanMap::RealSpanMap(&span_map); |
| |
| // Build the subtree and token mapping for the speculative args |
| let (mut tt, undo_info) = match &loc.kind { |
| MacroCallKind::FnLike { .. } => ( |
| syntax_bridge::syntax_node_to_token_tree( |
| speculative_args, |
| span_map, |
| span, |
| if loc.def.is_proc_macro() { |
| DocCommentDesugarMode::ProcMacro |
| } else { |
| DocCommentDesugarMode::Mbe |
| }, |
| ), |
| SyntaxFixupUndoInfo::NONE, |
| ), |
| MacroCallKind::Attr { .. } if loc.def.is_attribute_derive() => ( |
| syntax_bridge::syntax_node_to_token_tree( |
| speculative_args, |
| span_map, |
| span, |
| DocCommentDesugarMode::ProcMacro, |
| ), |
| SyntaxFixupUndoInfo::NONE, |
| ), |
| MacroCallKind::Derive { derive_macro_id, .. } => { |
| let MacroCallKind::Attr { censored_attr_ids: attr_ids, .. } = |
| &derive_macro_id.loc(db).kind |
| else { |
| unreachable!("`derive_macro_id` should be `MacroCallKind::Attr`"); |
| }; |
| attr_macro_input_to_token_tree( |
| db, |
| speculative_args, |
| span_map, |
| span, |
| true, |
| attr_ids, |
| loc.krate, |
| ) |
| } |
| MacroCallKind::Attr { censored_attr_ids: attr_ids, .. } => { |
| attr_macro_input_to_token_tree( |
| db, |
| speculative_args, |
| span_map, |
| span, |
| false, |
| attr_ids, |
| loc.krate, |
| ) |
| } |
| }; |
| |
| let attr_arg = match &loc.kind { |
| MacroCallKind::Attr { censored_attr_ids: attr_ids, .. } => { |
| if loc.def.is_attribute_derive() { |
| // for pseudo-derive expansion we actually pass the attribute itself only |
| ast::Attr::cast(speculative_args.clone()) |
| .and_then(|attr| { |
| if let ast::Meta::TokenTreeMeta(meta) = attr.meta()? { |
| meta.token_tree() |
| } else { |
| None |
| } |
| }) |
| .map(|token_tree| { |
| let mut tree = syntax_node_to_token_tree( |
| token_tree.syntax(), |
| span_map, |
| span, |
| DocCommentDesugarMode::ProcMacro, |
| ); |
| tree.set_top_subtree_delimiter_kind(tt::DelimiterKind::Invisible); |
| tree.set_top_subtree_delimiter_span(tt::DelimSpan::from_single(span)); |
| tree |
| }) |
| } else { |
| // Attributes may have an input token tree, build the subtree and map for this as well |
| // then try finding a token id for our token if it is inside this input subtree. |
| let item = ast::Item::cast(speculative_args.clone())?; |
| let (_, meta) = attr_ids |
| .invoc_attr() |
| .find_attr_range_with_source_opt(db, loc.krate, &item)?; |
| if let ast::Meta::TokenTreeMeta(meta) = meta |
| && let Some(tt) = meta.token_tree() |
| { |
| let mut attr_arg = syntax_bridge::syntax_node_to_token_tree( |
| tt.syntax(), |
| span_map, |
| span, |
| DocCommentDesugarMode::ProcMacro, |
| ); |
| attr_arg.set_top_subtree_delimiter_kind(tt::DelimiterKind::Invisible); |
| Some(attr_arg) |
| } else { |
| None |
| } |
| } |
| } |
| _ => None, |
| }; |
| |
| // Do the actual expansion, we need to directly expand the proc macro due to the attribute args |
| // Otherwise the expand query will fetch the non speculative attribute args and pass those instead. |
| let mut speculative_expansion = match loc.def.kind { |
| MacroDefKind::ProcMacro(ast, expander, _) => { |
| let span = proc_macro_span(db, ast); |
| tt.set_top_subtree_delimiter_kind(tt::DelimiterKind::Invisible); |
| tt.set_top_subtree_delimiter_span(tt::DelimSpan::from_single(span)); |
| expander.expand( |
| db, |
| loc.def.krate, |
| loc.krate, |
| &tt, |
| attr_arg.as_ref(), |
| span_with_def_site_ctxt(db, span, self.into(), loc.def.edition), |
| span_with_call_site_ctxt(db, span, self.into(), loc.def.edition), |
| span_with_mixed_site_ctxt(db, span, self.into(), loc.def.edition), |
| ) |
| } |
| MacroDefKind::BuiltInAttr(_, it) if it.is_derive() => { |
| pseudo_derive_attr_expansion(&tt, attr_arg.as_ref()?, span) |
| } |
| MacroDefKind::Declarative(it, _) => it |
| .decl_macro_expander(db, loc.krate) |
| .expand_unhygienic(db, &tt, loc.kind.call_style(), span), |
| MacroDefKind::BuiltIn(_, it) => it.expand(db, self, &tt, span).map_err(Into::into), |
| MacroDefKind::BuiltInDerive(_, it) => { |
| it.expand(db, self, &tt, span).map_err(Into::into) |
| } |
| MacroDefKind::BuiltInEager(_, it) => it.expand(db, self, &tt, span).map_err(Into::into), |
| MacroDefKind::BuiltInAttr(_, it) => it.expand(db, self, &tt, span), |
| MacroDefKind::UnimplementedBuiltIn(_) => expand_unimplemented_builtin_macro(span), |
| }; |
| |
| let expand_to = loc.expand_to(); |
| |
| fixup::reverse_fixups(&mut speculative_expansion.value, &undo_info); |
| let (node, rev_tmap) = |
| token_tree_to_syntax_node(db, &speculative_expansion.value, expand_to); |
| |
| let syntax_node = node.syntax_node(); |
| let token = rev_tmap |
| .ranges_with_span(span_map.span_for_range(token_to_map.text_range())) |
| .filter_map(|(range, ctx)| { |
| syntax_node.covering_element(range).into_token().zip(Some(ctx)) |
| }) |
| .map(|(t, ctx)| { |
| // prefer tokens of the same kind and text, as well as non opaque marked ones |
| // Note the inversion of the score here, as we want to prefer the first token in case |
| // of all tokens having the same score |
| let ranking = ctx.is_opaque(db) as u8 |
| + 2 * (t.kind() != token_to_map.kind()) as u8 |
| + 4 * ((t.text() != token_to_map.text()) as u8); |
| (t, ranking) |
| }) |
| .collect(); |
| Some((node.syntax_node(), token)) |
| } |
| } |
| |
| fn expand_unimplemented_builtin_macro(span: Span) -> ExpandResult<tt::TopSubtree> { |
| ExpandResult::new( |
| tt::TopSubtree::empty(tt::DelimSpan::from_single(span)), |
| ExpandError::other(span, "this built-in macro is not implemented"), |
| ) |
| } |
| |
| /// Retrieves the span to be used for a proc-macro expansions spans. |
| /// This is a firewall query as it requires parsing the file, which we don't want proc-macros to |
| /// directly depend on as that would cause to frequent invalidations, mainly because of the |
| /// parse queries being LRU cached. If they weren't the invalidations would only happen if the |
| /// user wrote in the file that defines the proc-macro. |
| fn proc_macro_span(db: &dyn SourceDatabase, ast: AstId<ast::Fn>) -> Span { |
| #[salsa::tracked(returns(copy))] |
| fn proc_macro_span(db: &dyn SourceDatabase, ast: AstId<ast::Fn>, _: ()) -> Span { |
| let (parse, span_map) = ast.file_id.parse_with_map(db); |
| let root = parse.syntax_node(); |
| let ast_id_map = ast.file_id.ast_id_map(db); |
| |
| let node = ast_id_map.get(ast.value).to_node(&root); |
| let range = ast::HasName::name(&node) |
| .map_or_else(|| node.syntax().text_range(), |name| name.syntax().text_range()); |
| span_map.span_for_range(range) |
| } |
| proc_macro_span(db, ast, ()) |
| } |
| |
| pub(crate) fn token_tree_to_syntax_node( |
| db: &dyn SourceDatabase, |
| tt: &tt::TopSubtree, |
| expand_to: ExpandTo, |
| ) -> (Parse<SyntaxNode>, ExpansionSpanMap) { |
| let entry_point = match expand_to { |
| ExpandTo::Statements => syntax_bridge::TopEntryPoint::MacroStmts, |
| ExpandTo::Items => syntax_bridge::TopEntryPoint::MacroItems, |
| ExpandTo::Pattern => syntax_bridge::TopEntryPoint::Pattern, |
| ExpandTo::Type => syntax_bridge::TopEntryPoint::Type, |
| ExpandTo::Expr => syntax_bridge::TopEntryPoint::Expr, |
| }; |
| syntax_bridge::token_tree_to_syntax_node(tt, entry_point, &mut |ctx| ctx.edition(db)) |
| } |
| |
| fn check_tt_count(tt: &tt::TopSubtree) -> Result<(), ExpandResult<()>> { |
| let tt = tt.top_subtree(); |
| let count = tt.count(); |
| if count <= TOKEN_LIMIT { |
| Ok(()) |
| } else { |
| Err(ExpandResult { |
| value: (), |
| err: Some(ExpandError::other( |
| tt.delimiter.open, |
| format!( |
| "macro invocation exceeds token limit: produced {count} tokens, limit is {TOKEN_LIMIT}", |
| ), |
| )), |
| }) |
| } |
| } |
| |
| impl MacroDefId { |
| pub fn make_call( |
| self, |
| db: &dyn SourceDatabase, |
| krate: Crate, |
| kind: MacroCallKind, |
| ctxt: SyntaxContext, |
| macro_depth: u32, |
| ) -> MacroCallId { |
| MacroCallId::new(db, MacroCallLoc { def: self, krate, kind, ctxt, macro_depth }) |
| } |
| |
| pub fn definition_range(&self, db: &dyn SourceDatabase) -> InFile<TextRange> { |
| match self.kind { |
| MacroDefKind::Declarative(id, _) |
| | MacroDefKind::BuiltIn(id, _) |
| | MacroDefKind::BuiltInAttr(id, _) |
| | MacroDefKind::BuiltInDerive(id, _) |
| | MacroDefKind::BuiltInEager(id, _) |
| | MacroDefKind::UnimplementedBuiltIn(id) => { |
| id.with_value(id.file_id.ast_id_map(db).get(id.value).text_range()) |
| } |
| MacroDefKind::ProcMacro(id, _, _) => { |
| id.with_value(id.file_id.ast_id_map(db).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, _) |
| | MacroDefKind::UnimplementedBuiltIn(id) => Either::Left(id), |
| } |
| } |
| |
| pub fn is_proc_macro(&self) -> bool { |
| matches!(self.kind, MacroDefKind::ProcMacro(..)) |
| } |
| |
| pub fn is_attribute(&self) -> bool { |
| match self.kind { |
| MacroDefKind::BuiltInAttr(..) |
| | MacroDefKind::ProcMacro(_, _, ProcMacroKind::Attr) |
| | MacroDefKind::UnimplementedBuiltIn(_) => true, |
| MacroDefKind::Declarative(_, styles) => styles.contains(MacroCallStyles::ATTR), |
| _ => false, |
| } |
| } |
| |
| pub fn is_derive(&self) -> bool { |
| match self.kind { |
| MacroDefKind::BuiltInDerive(..) |
| | MacroDefKind::ProcMacro(_, _, ProcMacroKind::CustomDerive) |
| | MacroDefKind::UnimplementedBuiltIn(_) => true, |
| MacroDefKind::Declarative(_, styles) => styles.contains(MacroCallStyles::DERIVE), |
| _ => false, |
| } |
| } |
| |
| pub fn is_fn_like(&self) -> bool { |
| matches!( |
| self.kind, |
| MacroDefKind::BuiltIn(..) |
| | MacroDefKind::ProcMacro(_, _, ProcMacroKind::Bang) |
| | MacroDefKind::BuiltInEager(..) |
| | MacroDefKind::Declarative(..) |
| | MacroDefKind::UnimplementedBuiltIn(_) |
| ) |
| } |
| |
| 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 SourceDatabase) -> 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, .. } => { |
| let (_, attr) = derive_attr_index.find_attr_range(db, self.krate, *ast_id); |
| ast_id.with_value(attr.syntax().clone()) |
| } |
| MacroCallKind::Attr { ast_id, censored_attr_ids: attr_ids, .. } => { |
| if self.def.is_attribute_derive() { |
| let (_, attr) = attr_ids.invoc_attr().find_attr_range(db, self.krate, *ast_id); |
| ast_id.with_value(attr.syntax().clone()) |
| } else { |
| ast_id.with_value(ast_id.to_node(db).syntax().clone()) |
| } |
| } |
| } |
| } |
| |
| pub fn to_node_item(&self, db: &dyn SourceDatabase) -> 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 SourceDatabase, |
| macro_call_id: MacroCallId, |
| ) -> Option<EditionedFileId> { |
| if self.def.is_include() |
| && let MacroCallKind::FnLike { eager: Some(eager), .. } = &self.kind |
| && let Ok(it) = include_input_to_file_id(db, macro_call_id, &eager.arg) |
| { |
| return Some(it); |
| } |
| |
| None |
| } |
| } |
| |
| impl MacroCallKind { |
| pub 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. |
| /// |
| /// This spans the entire macro call, including its input. That is for |
| /// - fn_like! {}, it spans the path and token tree |
| /// - #\[derive], it spans the `#[derive(...)]` attribute and the annotated item |
| /// - #\[attr], it spans the `#[attr(...)]` attribute and the annotated item |
| pub fn original_call_range_with_input(&self, db: &dyn SourceDatabase) -> FileRange { |
| let get_range = |kind: &_| match kind { |
| MacroCallKind::FnLike { ast_id, .. } => ast_id.erase(), |
| MacroCallKind::Derive { ast_id, .. } => ast_id.erase(), |
| MacroCallKind::Attr { ast_id, .. } => ast_id.erase(), |
| }; |
| |
| let mut ast_id = get_range(self); |
| let mut file_id = self.file_id(); |
| let file_id = loop { |
| match file_id { |
| HirFileId::MacroFile(file) => { |
| let kind = &file.loc(db).kind; |
| ast_id = get_range(kind); |
| file_id = kind.file_id(); |
| } |
| HirFileId::FileId(file_id) => break file_id, |
| } |
| }; |
| |
| FileRange { range: ast_id.to_ptr(db).text_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 macro path (rustc shows 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 SourceDatabase, krate: Crate) -> FileRange { |
| let get_range = |kind: &_| match kind { |
| MacroCallKind::FnLike { ast_id, .. } => { |
| let node = ast_id.to_node(db); |
| node.path() |
| .unwrap() |
| .syntax() |
| .text_range() |
| .cover(node.excl_token().unwrap().text_range()) |
| } |
| MacroCallKind::Derive { ast_id, derive_attr_index, .. } => { |
| // FIXME: should be the range of the macro name, not the whole derive |
| derive_attr_index.find_attr_range(db, krate, *ast_id).1.syntax().text_range() |
| } |
| // FIXME: handle `cfg_attr` |
| MacroCallKind::Attr { ast_id, censored_attr_ids: attr_ids, .. } => { |
| attr_ids.invoc_attr().find_attr_range(db, krate, *ast_id).1.syntax().text_range() |
| } |
| }; |
| |
| let mut range = get_range(self); |
| let mut file_id = self.file_id(); |
| let file_id = loop { |
| match file_id { |
| HirFileId::MacroFile(file) => { |
| let kind = &file.loc(db).kind; |
| range = get_range(kind); |
| file_id = kind.file_id(); |
| } |
| HirFileId::FileId(file_id) => break file_id, |
| } |
| }; |
| |
| FileRange { range, file_id } |
| } |
| |
| fn arg(&self, db: &dyn SourceDatabase) -> 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<'db> { |
| expanded: InMacroFile<SyntaxNode>, |
| /// The argument TokenTree or item for attributes |
| arg: InFile<Option<SyntaxNode>>, |
| exp_map: &'db ExpansionSpanMap, |
| arg_map: SpanMap<'db>, |
| loc: &'db MacroCallLoc, |
| } |
| |
| impl<'db> ExpansionInfo<'db> { |
| 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. |
| // FIXME: Consider adding a reverse map to ExpansionInfo to get rid of the linear search which |
| // potentially results in quadratic look ups (notably this might improve semantic highlighting perf) |
| pub fn map_range_down_exact( |
| &self, |
| span: Span, |
| ) -> Option<InMacroFile<impl Iterator<Item = (SyntaxToken, SyntaxContext)> + '_>> { |
| if span.anchor.ast_id == NO_DOWNMAP_ERASED_FILE_AST_ID_MARKER { |
| return None; |
| } |
| |
| let tokens = self.exp_map.ranges_with_span_exact(span).flat_map(move |(range, ctx)| { |
| self.expanded.value.covering_element(range).into_token().zip(Some(ctx)) |
| }); |
| |
| 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 [`ExpansionInfo::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, SyntaxContext)> + '_>> { |
| if span.anchor.ast_id == NO_DOWNMAP_ERASED_FILE_AST_ID_MARKER { |
| return None; |
| } |
| |
| let tokens = self.exp_map.ranges_with_span(span).flat_map(move |(range, ctx)| { |
| self.expanded.value.covering_element(range).into_token().zip(Some(ctx)) |
| }); |
| |
| Some(InMacroFile::new(self.expanded.file_id, tokens)) |
| } |
| |
| /// Looks up the span at the given offset. |
| pub fn span_for_offset( |
| &self, |
| db: &dyn SourceDatabase, |
| offset: TextSize, |
| ) -> (FileRange, SyntaxContext) { |
| 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 SourceDatabase, |
| range: TextRange, |
| ) -> Option<(FileRange, SyntaxContext)> { |
| 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 its macro call. |
| /// |
| /// Note that this may return multiple ranges as we lose the precise association between input to output |
| /// and as such we may consider inputs that are unrelated. |
| pub fn map_range_up_once( |
| &self, |
| db: &dyn SourceDatabase, |
| 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 range = resolve_span(db, span); |
| InFile { file_id: range.file_id.into(), value: smallvec::smallvec![range.range] } |
| } |
| SpanMap::ExpansionSpanMap(arg_map) => { |
| let Some(arg_node) = &self.arg.value else { |
| return InFile::new(self.arg.file_id, smallvec::smallvec![]); |
| }; |
| let arg_range = arg_node.text_range(); |
| InFile::new( |
| self.arg.file_id, |
| arg_map |
| .ranges_with_span_exact(span) |
| .map(|(range, _)| range) |
| .filter(|range| range.intersect(arg_range).is_some()) |
| .collect(), |
| ) |
| } |
| } |
| } |
| |
| pub fn new(db: &'db dyn SourceDatabase, macro_file: MacroCallId) -> ExpansionInfo<'db> { |
| let _p = tracing::info_span!("ExpansionInfo::new").entered(); |
| let loc = macro_file.loc(db); |
| |
| let arg_tt = loc.kind.arg(db); |
| let arg_map = arg_tt.file_id.span_map(db); |
| |
| let (parse, exp_map) = ¯o_file.parse_macro_expansion(db).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 SourceDatabase, |
| 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()); |
| } |
| Some(resolve_span(db, Span { range: TextRange::new(start, end), anchor, ctx })) |
| } |
| |
| /// 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 SourceDatabase, |
| exp_map: &ExpansionSpanMap, |
| range: TextRange, |
| ) -> Option<(FileRange, SyntaxContext)> { |
| 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()); |
| } |
| Some((resolve_span(db, Span { range: TextRange::new(start, end), anchor, ctx }), ctx)) |
| } |
| |
| /// Looks up the span at the given offset. |
| pub fn span_for_offset( |
| db: &dyn SourceDatabase, |
| exp_map: &ExpansionSpanMap, |
| offset: TextSize, |
| ) -> (FileRange, SyntaxContext) { |
| let span = exp_map.span_at(offset); |
| (resolve_span(db, span), span.ctx) |
| } |
| |
| // FIXME: This is only public because of its use in `load_cargo` (which we should consider removing |
| // by moving the implementations of the subrequests to `hir_expand`, and calling within `load-cargo`). |
| // Avoid adding any more outside uses. |
| pub fn resolve_span(db: &dyn SourceDatabase, Span { range, anchor, ctx: _ }: Span) -> FileRange { |
| let file_id = EditionedFileId::from_span_file_id(db, anchor.file_id); |
| let anchor_offset = |
| HirFileId::from(file_id).ast_id_map(db).get_erased(anchor.ast_id).text_range().start(); |
| FileRange { file_id, range: range + anchor_offset } |
| } |
| |
| /// 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() |
| .is_some_and(|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 |
| } |
| } |
| } |
| } |
| |
| /// Macro ids. That's probably the tricksiest bit in rust-analyzer, and the |
| /// reason why we use salsa at all. |
| /// |
| /// We encode macro definitions into ids of macro calls, this what allows us |
| /// to be incremental. |
| #[salsa::interned(unsafe(no_lifetime), debug, revisions = usize::MAX)] |
| #[doc(alias = "MacroFileId")] |
| pub struct MacroCallId { |
| #[returns(ref)] |
| pub loc: MacroCallLoc, |
| } |
| |
| impl From<span::MacroCallId> for MacroCallId { |
| #[inline] |
| fn from(value: span::MacroCallId) -> Self { |
| MacroCallId::from_id(value.0) |
| } |
| } |
| |
| impl From<MacroCallId> for span::MacroCallId { |
| #[inline] |
| fn from(value: MacroCallId) -> span::MacroCallId { |
| span::MacroCallId(value.as_id()) |
| } |
| } |
| |
| #[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, salsa::Supertype)] |
| pub enum HirFileId { |
| FileId(EditionedFileId), |
| MacroFile(MacroCallId), |
| } |
| |
| impl From<EditionedFileId> for HirFileId { |
| #[inline] |
| fn from(file_id: EditionedFileId) -> Self { |
| HirFileId::FileId(file_id) |
| } |
| } |
| |
| impl From<MacroCallId> for HirFileId { |
| #[inline] |
| fn from(file_id: MacroCallId) -> Self { |
| HirFileId::MacroFile(file_id) |
| } |
| } |
| |
| impl PartialEq<EditionedFileId> for HirFileId { |
| fn eq(&self, &other: &EditionedFileId) -> bool { |
| *self == HirFileId::from(other) |
| } |
| } |
| impl PartialEq<HirFileId> for EditionedFileId { |
| fn eq(&self, &other: &HirFileId) -> bool { |
| other == HirFileId::from(*self) |
| } |
| } |
| |
| impl HirFileId { |
| #[inline] |
| pub fn macro_file(self) -> Option<MacroCallId> { |
| match self { |
| HirFileId::FileId(_) => None, |
| HirFileId::MacroFile(it) => Some(it), |
| } |
| } |
| |
| #[inline] |
| pub fn is_macro(self) -> bool { |
| matches!(self, HirFileId::MacroFile(_)) |
| } |
| |
| #[inline] |
| pub fn file_id(self) -> Option<EditionedFileId> { |
| match self { |
| HirFileId::FileId(it) => Some(it), |
| HirFileId::MacroFile(_) => None, |
| } |
| } |
| |
| pub fn syntax_context(self, db: &dyn SourceDatabase, edition: Edition) -> SyntaxContext { |
| match self { |
| HirFileId::FileId(_) => SyntaxContext::root(edition), |
| HirFileId::MacroFile(m) => { |
| let kind = &m.loc(db).kind; |
| m.macro_arg_considering_derives(db, kind).2.ctx |
| } |
| } |
| } |
| |
| pub fn edition(self, db: &dyn SourceDatabase) -> Edition { |
| match self { |
| HirFileId::FileId(file_id) => file_id.edition(db), |
| HirFileId::MacroFile(m) => m.loc(db).def.edition, |
| } |
| } |
| |
| pub fn original_file(self, db: &dyn SourceDatabase) -> EditionedFileId { |
| let mut file_id = self; |
| loop { |
| match file_id { |
| HirFileId::FileId(id) => break id, |
| HirFileId::MacroFile(macro_call_id) => { |
| file_id = macro_call_id.loc(db).kind.file_id() |
| } |
| } |
| } |
| } |
| |
| pub fn original_file_respecting_includes(mut self, db: &dyn SourceDatabase) -> EditionedFileId { |
| loop { |
| match self { |
| HirFileId::FileId(id) => break id, |
| HirFileId::MacroFile(file) => { |
| let loc = file.loc(db); |
| if loc.def.is_include() |
| && let MacroCallKind::FnLike { eager: Some(eager), .. } = &loc.kind |
| && let Ok(it) = include_input_to_file_id(db, file, &eager.arg) |
| { |
| break it; |
| } |
| self = loc.kind.file_id(); |
| } |
| } |
| } |
| } |
| |
| pub fn original_call_node(self, db: &dyn SourceDatabase) -> Option<InRealFile<SyntaxNode>> { |
| let mut call = self.macro_file()?.loc(db).to_node(db); |
| loop { |
| match call.file_id { |
| HirFileId::FileId(file_id) => { |
| break Some(InRealFile { file_id, value: call.value }); |
| } |
| HirFileId::MacroFile(macro_call_id) => { |
| call = macro_call_id.loc(db).to_node(db); |
| } |
| } |
| } |
| } |
| |
| pub fn call_node(self, db: &dyn SourceDatabase) -> Option<InFile<SyntaxNode>> { |
| Some(self.macro_file()?.loc(db).to_node(db)) |
| } |
| |
| pub fn as_builtin_derive_attr_node( |
| &self, |
| db: &dyn SourceDatabase, |
| ) -> Option<InFile<ast::Attr>> { |
| let macro_file = self.macro_file()?; |
| let loc = macro_file.loc(db); |
| let attr = match loc.def.kind { |
| MacroDefKind::BuiltInDerive(..) => loc.to_node(db), |
| _ => return None, |
| }; |
| Some(attr.with_value(ast::Attr::cast(attr.value.clone())?)) |
| } |
| |
| /// Main public API -- parses a hir file, not caring whether it's a real |
| /// file or a macro expansion. |
| pub fn parse_or_expand(self, db: &dyn SourceDatabase) -> SyntaxNode { |
| match self { |
| HirFileId::FileId(file_id) => file_id.parse(db).syntax_node(), |
| HirFileId::MacroFile(macro_file) => { |
| macro_file.parse_macro_expansion(db).value.0.syntax_node() |
| } |
| } |
| } |
| |
| pub(crate) fn parse_with_map( |
| self, |
| db: &dyn SourceDatabase, |
| ) -> (Parse<SyntaxNode>, SpanMap<'_>) { |
| match self { |
| HirFileId::FileId(file_id) => ( |
| file_id.parse(db).to_syntax(), |
| SpanMap::RealSpanMap(crate::span_map::real_span_map(db, file_id)), |
| ), |
| HirFileId::MacroFile(macro_file) => { |
| let (parse, map) = ¯o_file.parse_macro_expansion(db).value; |
| (parse.clone(), SpanMap::ExpansionSpanMap(map)) |
| } |
| } |
| } |
| |
| /// How many macro expansions separate this file from the real file it originates from. |
| /// |
| /// `Expander` and `DefCollector` seed their expansion depth counters from this. Both otherwise |
| /// start at zero for every body and every block, so a macro expanding to an item that invokes |
| /// the macro again advances neither counter past one and never reaches the recursion limit. |
| /// |
| /// Written recursively rather than as a loop so that salsa memoizes each file's depth. Callers |
| /// ask for this once per body, and the bodies of one expansion chain sit at depths 1..n, so a |
| /// loop would walk 1 + 2 + ... + n steps for that chain. Reusing the parent's memoized depth |
| /// makes each file O(1) once computed, and the chain linear. |
| #[inline] |
| pub fn macro_expansion_depth(self, db: &dyn SourceDatabase) -> u32 { |
| match self { |
| HirFileId::MacroFile(macro_call) => macro_call.loc(db).macro_depth, |
| HirFileId::FileId(_) => 0, |
| } |
| } |
| } |
| |
| #[salsa::tracked] |
| impl HirFileId { |
| #[salsa::tracked(lru = 1024, returns(ref))] |
| pub fn ast_id_map(self, db: &dyn SourceDatabase) -> AstIdMap { |
| AstIdMap::from_source(&self.parse_or_expand(db)) |
| } |
| } |