Unnamed repository; edit this file 'description' to name the repository.
Diffstat (limited to 'crates/hir-expand/src/lib.rs')
-rw-r--r--crates/hir-expand/src/lib.rs571
1 files changed, 485 insertions, 86 deletions
diff --git a/crates/hir-expand/src/lib.rs b/crates/hir-expand/src/lib.rs
index 0ba04bc395..719a38edf4 100644
--- a/crates/hir-expand/src/lib.rs
+++ b/crates/hir-expand/src/lib.rs
@@ -33,17 +33,20 @@ use thin_vec::ThinVec;
use triomphe::Arc;
use core::fmt;
-use std::{hash::Hash, ops};
+use std::{borrow::Cow, ops};
use base_db::Crate;
use either::Either;
+use mbe::MatchedArmIndex;
use span::{
- Edition, ErasedFileAstId, FileAstId, NO_DOWNMAP_ERASED_FILE_AST_ID_MARKER, Span, SyntaxContext,
+ AstIdMap, Edition, ErasedFileAstId, FileAstId, NO_DOWNMAP_ERASED_FILE_AST_ID_MARKER, Span,
+ SyntaxContext,
};
use syntax::{
- SyntaxNode, SyntaxToken, TextRange, TextSize,
+ Parse, SyntaxError, SyntaxNode, SyntaxToken, T, TextRange, TextSize,
ast::{self, AstNode},
};
+use syntax_bridge::DocCommentDesugarMode;
use crate::{
attrs::AttrId,
@@ -51,8 +54,10 @@ use crate::{
BuiltinAttrExpander, BuiltinDeriveExpander, BuiltinFnLikeExpander, EagerExpander,
include_input_to_file_id,
},
+ cfg_process::attr_macro_input_to_token_tree,
db::ExpandDatabase,
- mod_path::ModPath,
+ 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, SpanMap},
};
@@ -67,6 +72,18 @@ 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 {
($db:ident, $id:ident, $loc:ident) => {
@@ -378,75 +395,6 @@ impl MacroCallKind {
}
}
-impl HirFileId {
- pub fn edition(self, db: &dyn ExpandDatabase) -> 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 ExpandDatabase) -> 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 ExpandDatabase) -> 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 ExpandDatabase) -> 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 ExpandDatabase) -> Option<InFile<SyntaxNode>> {
- Some(self.macro_file()?.loc(db).to_node(db))
- }
-
- pub fn as_builtin_derive_attr_node(
- &self,
- db: &dyn ExpandDatabase,
- ) -> 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())?))
- }
-}
-
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub enum MacroKind {
/// `macro_rules!` or Macros 2.0 macro.
@@ -537,6 +485,357 @@ impl MacroCallId {
}
}
+#[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 ExpandDatabase,
+ ) -> 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 ExpandDatabase,
+ ) -> 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 ExpandDatabase,
+ 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 ExpandDatabase) -> 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!("`ExpandDatabase::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 ExpandDatabase,
+ 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, _) => {
+ db.decl_macro_expander(loc.def.krate, id).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 ExpandDatabase) -> 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 }
+ }
+}
+
+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 ExpandDatabase, ast: AstId<ast::Fn>) -> Span {
+ #[salsa::tracked]
+ fn proc_macro_span(db: &dyn ExpandDatabase, ast: AstId<ast::Fn>, _: ()) -> Span {
+ let root = ast.file_id.parse_or_expand(db);
+ let ast_id_map = ast.file_id.ast_id_map(db);
+ let span_map = db.span_map(ast.file_id);
+
+ 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 ExpandDatabase,
+ 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,
@@ -556,10 +855,10 @@ impl MacroDefId {
| MacroDefKind::BuiltInDerive(id, _)
| MacroDefKind::BuiltInEager(id, _)
| MacroDefKind::UnimplementedBuiltIn(id) => {
- id.with_value(db.ast_id_map(id.file_id).get(id.value).text_range())
+ id.with_value(id.file_id.ast_id_map(db).get(id.value).text_range())
}
MacroDefKind::ProcMacro(id, _, _) => {
- id.with_value(db.ast_id_map(id.file_id).get(id.value).text_range())
+ id.with_value(id.file_id.ast_id_map(db).get(id.value).text_range())
}
}
}
@@ -932,7 +1231,7 @@ impl<'db> ExpansionInfo<'db> {
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 (parse, exp_map) = &macro_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 }
@@ -1059,8 +1358,6 @@ impl ExpandTo {
}
}
-intern::impl_internable!(ModPath);
-
/// Macro ids. That's probably the tricksiest bit in rust-analyzer, and the
/// reason why we use salsa at all.
///
@@ -1107,6 +1404,17 @@ impl From<MacroCallId> for HirFileId {
}
}
+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> {
@@ -1134,19 +1442,110 @@ impl HirFileId {
HirFileId::FileId(_) => SyntaxContext::root(edition),
HirFileId::MacroFile(m) => {
let kind = &m.loc(db).kind;
- db.macro_arg_considering_derives(m, kind).2.ctx
+ m.macro_arg_considering_derives(db, kind).2.ctx
}
}
}
-}
-impl PartialEq<EditionedFileId> for HirFileId {
- fn eq(&self, &other: &EditionedFileId) -> bool {
- *self == HirFileId::from(other)
+ pub fn edition(self, db: &dyn ExpandDatabase) -> 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 ExpandDatabase) -> 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 ExpandDatabase) -> 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 ExpandDatabase) -> 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 ExpandDatabase) -> Option<InFile<SyntaxNode>> {
+ Some(self.macro_file()?.loc(db).to_node(db))
+ }
+
+ pub fn as_builtin_derive_attr_node(
+ &self,
+ db: &dyn ExpandDatabase,
+ ) -> 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 ExpandDatabase) -> 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 ExpandDatabase,
+ ) -> (Parse<SyntaxNode>, SpanMap<'_>) {
+ match self {
+ HirFileId::FileId(file_id) => {
+ (file_id.parse(db).to_syntax(), SpanMap::RealSpanMap(db.real_span_map(file_id)))
+ }
+ HirFileId::MacroFile(macro_file) => {
+ let (parse, map) = &macro_file.parse_macro_expansion(db).value;
+ (parse.clone(), SpanMap::ExpansionSpanMap(map))
+ }
+ }
}
}
-impl PartialEq<HirFileId> for EditionedFileId {
- fn eq(&self, &other: &HirFileId) -> bool {
- other == HirFileId::from(*self)
+
+#[salsa::tracked]
+impl HirFileId {
+ #[salsa::tracked(lru = 1024, returns(ref))]
+ pub fn ast_id_map(self, db: &dyn ExpandDatabase) -> AstIdMap {
+ AstIdMap::from_source(&self.parse_or_expand(db))
}
}