| //! Defines database & queries for macro expansion. |
| |
| use base_db::{Crate, SourceDatabase}; |
| use mbe::MatchedArmIndex; |
| use span::{AstIdMap, Edition, Span, SyntaxContext}; |
| use std::borrow::Cow; |
| use syntax::{AstNode, Parse, SyntaxError, SyntaxNode, SyntaxToken, T, ast}; |
| use syntax_bridge::{DocCommentDesugarMode, syntax_node_to_token_tree}; |
| use triomphe::Arc; |
| |
| use crate::{ |
| AstId, BuiltinAttrExpander, BuiltinDeriveExpander, BuiltinFnLikeExpander, EagerCallInfo, |
| EagerExpander, EditionedFileId, ExpandError, ExpandResult, ExpandTo, FileRange, HirFileId, |
| MacroCallId, MacroCallKind, MacroCallLoc, MacroDefId, MacroDefKind, |
| builtin::pseudo_derive_attr_expansion, |
| cfg_process::attr_macro_input_to_token_tree, |
| declarative::DeclarativeMacroExpander, |
| fixup::{self, SyntaxFixupUndoInfo}, |
| hygiene::{span_with_call_site_ctxt, span_with_def_site_ctxt, span_with_mixed_site_ctxt}, |
| proc_macro::{CrateProcMacros, CustomProcMacroExpander, ProcMacros}, |
| span_map::{ExpansionSpanMap, RealSpanMap, SpanMap}, |
| tt, |
| }; |
| /// This is just to ensure the types of smart_macro_arg and 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; |
| |
| #[derive(Debug, Clone, Copy, Eq, PartialEq)] |
| pub enum TokenExpander<'db> { |
| /// Old-style `macro_rules` or the new macros 2.0 |
| DeclarativeMacro(&'db DeclarativeMacroExpander), |
| /// Stuff like `line!` and `file!`. |
| BuiltIn(BuiltinFnLikeExpander), |
| /// Built-in eagerly expanded fn-like macros (`include!`, `concat!`, etc.) |
| BuiltInEager(EagerExpander), |
| /// `global_allocator` and such. |
| BuiltInAttr(BuiltinAttrExpander), |
| /// `derive(Copy)` and such. |
| BuiltInDerive(BuiltinDeriveExpander), |
| UnimplementedBuiltIn, |
| /// The thing we love the most here in rust-analyzer -- procedural macros. |
| ProcMacro(CustomProcMacroExpander), |
| } |
| |
| #[query_group::query_group] |
| pub trait ExpandDatabase: SourceDatabase { |
| /// The proc macros. Do not use this! Use `proc_macros_for_crate()` instead. |
| #[salsa::input] |
| fn proc_macros(&self) -> Arc<ProcMacros>; |
| |
| /// Incrementality query to prevent queries from directly depending on `ExpandDatabase::proc_macros`. |
| #[salsa::invoke(crate::proc_macro::proc_macros_for_crate)] |
| fn proc_macros_for_crate(&self, krate: Crate) -> Option<Arc<CrateProcMacros>>; |
| |
| #[salsa::invoke(ast_id_map)] |
| #[salsa::transparent] |
| fn ast_id_map(&self, file_id: HirFileId) -> &AstIdMap; |
| |
| #[salsa::transparent] |
| fn resolve_span(&self, span: Span) -> FileRange; |
| |
| #[salsa::transparent] |
| fn parse_or_expand(&self, file_id: HirFileId) -> SyntaxNode; |
| |
| /// Implementation for the macro case. |
| #[salsa::transparent] |
| fn parse_macro_expansion( |
| &self, |
| macro_file: MacroCallId, |
| ) -> &ExpandResult<(Parse<SyntaxNode>, ExpansionSpanMap)>; |
| |
| #[salsa::transparent] |
| #[salsa::invoke(SpanMap::new)] |
| fn span_map(&self, file_id: HirFileId) -> SpanMap<'_>; |
| |
| #[salsa::transparent] |
| #[salsa::invoke(crate::span_map::expansion_span_map)] |
| fn expansion_span_map(&self, file_id: MacroCallId) -> &ExpansionSpanMap; |
| #[salsa::invoke(crate::span_map::real_span_map)] |
| #[salsa::transparent] |
| fn real_span_map(&self, file_id: EditionedFileId) -> &RealSpanMap; |
| |
| /// 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. |
| #[deprecated = "calling this is incorrect, call `macro_arg_considering_derives` instead"] |
| #[salsa::invoke(macro_arg)] |
| #[salsa::transparent] |
| fn macro_arg(&self, id: MacroCallId) -> &MacroArgResult; |
| |
| #[salsa::transparent] |
| fn macro_arg_considering_derives<'db>( |
| &'db self, |
| id: MacroCallId, |
| kind: &MacroCallKind, |
| ) -> &'db MacroArgResult; |
| |
| /// Fetches the expander for this macro. |
| #[salsa::transparent] |
| #[salsa::invoke(TokenExpander::macro_expander)] |
| fn macro_expander(&self, id: MacroDefId) -> TokenExpander<'_>; |
| |
| /// Fetches (and compiles) the expander of this decl macro. |
| #[salsa::invoke(DeclarativeMacroExpander::expander)] |
| #[salsa::transparent] |
| fn decl_macro_expander( |
| &self, |
| def_crate: Crate, |
| id: AstId<ast::Macro>, |
| ) -> &DeclarativeMacroExpander; |
| |
| /// Special case of the previous query 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::invoke(expand_proc_macro)] |
| #[salsa::transparent] |
| fn expand_proc_macro(&self, call: MacroCallId) -> &ExpandResult<tt::TopSubtree>; |
| /// 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. |
| #[salsa::invoke_interned(proc_macro_span)] |
| fn proc_macro_span(&self, fun: AstId<ast::Fn>) -> Span; |
| |
| #[salsa::invoke(parse_macro_expansion_error)] |
| #[salsa::transparent] |
| fn parse_macro_expansion_error( |
| &self, |
| macro_call: MacroCallId, |
| ) -> Option<ExpandResult<Arc<[SyntaxError]>>>; |
| |
| #[salsa::transparent] |
| fn syntax_context(&self, file: HirFileId, edition: Edition) -> SyntaxContext; |
| } |
| |
| #[salsa_macros::interned(no_lifetime, id = span::SyntaxContext, revisions = usize::MAX)] |
| pub struct SyntaxContextWrapper { |
| pub data: SyntaxContext, |
| } |
| |
| fn syntax_context(db: &dyn ExpandDatabase, file: HirFileId, edition: Edition) -> SyntaxContext { |
| match file { |
| HirFileId::FileId(_) => SyntaxContext::root(edition), |
| HirFileId::MacroFile(m) => { |
| let kind = &m.loc(db).kind; |
| db.macro_arg_considering_derives(m, kind).2.ctx |
| } |
| } |
| } |
| |
| fn resolve_span(db: &dyn ExpandDatabase, Span { range, anchor, ctx: _ }: Span) -> FileRange { |
| let file_id = EditionedFileId::from_span_file_id(db, anchor.file_id); |
| let anchor_offset = |
| db.ast_id_map(file_id.into()).get_erased(anchor.ast_id).text_range().start(); |
| FileRange { file_id, range: range + anchor_offset } |
| } |
| |
| /// 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( |
| db: &dyn ExpandDatabase, |
| actual_macro_call: MacroCallId, |
| speculative_args: &SyntaxNode, |
| token_to_map: SyntaxToken, |
| ) -> Option<(SyntaxNode, Vec<(SyntaxToken, u8)>)> { |
| let loc = actual_macro_call.loc(db); |
| let (_, _, span) = *db.macro_arg_considering_derives(actual_macro_call, &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(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 = db.proc_macro_span(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, actual_macro_call.into(), loc.def.edition), |
| span_with_call_site_ctxt(db, span, actual_macro_call.into(), loc.def.edition), |
| span_with_mixed_site_ctxt(db, span, actual_macro_call.into(), loc.def.edition), |
| ) |
| } |
| MacroDefKind::BuiltInAttr(_, it) if it.is_derive() => { |
| pseudo_derive_attr_expansion(&tt, attr_arg.as_ref()?, span) |
| } |
| MacroDefKind::Declarative(it, _) => db |
| .decl_macro_expander(loc.krate, it) |
| .expand_unhygienic(db, tt, loc.kind.call_style(), span), |
| MacroDefKind::BuiltIn(_, it) => { |
| it.expand(db, actual_macro_call, &tt, span).map_err(Into::into) |
| } |
| MacroDefKind::BuiltInDerive(_, it) => { |
| it.expand(db, actual_macro_call, &tt, span).map_err(Into::into) |
| } |
| MacroDefKind::BuiltInEager(_, it) => { |
| it.expand(db, actual_macro_call, &tt, span).map_err(Into::into) |
| } |
| MacroDefKind::BuiltInAttr(_, it) => it.expand(db, actual_macro_call, &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"), |
| ) |
| } |
| |
| #[salsa::tracked(lru = 1024, returns(ref))] |
| fn ast_id_map(db: &dyn ExpandDatabase, file_id: HirFileId) -> AstIdMap { |
| AstIdMap::from_source(&db.parse_or_expand(file_id)) |
| } |
| |
| /// Main public API -- parses a hir file, not caring whether it's a real |
| /// file or a macro expansion. |
| fn parse_or_expand(db: &dyn ExpandDatabase, file_id: HirFileId) -> SyntaxNode { |
| match file_id { |
| HirFileId::FileId(file_id) => file_id.parse(db).syntax_node(), |
| HirFileId::MacroFile(macro_file) => { |
| db.parse_macro_expansion(macro_file).value.0.syntax_node() |
| } |
| } |
| } |
| |
| // 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_macros::tracked(returns(ref), lru = 512)] |
| fn parse_macro_expansion( |
| db: &dyn ExpandDatabase, |
| macro_file: MacroCallId, |
| ) -> ExpandResult<(Parse<SyntaxNode>, ExpansionSpanMap)> { |
| let _p = tracing::info_span!("parse_macro_expansion").entered(); |
| let loc = macro_file.loc(db); |
| let expand_to = loc.expand_to(); |
| let mbe::ValueResult { value: (tt, matched_arm), err } = macro_expand(db, macro_file, 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 } |
| } |
| |
| fn parse_macro_expansion_error( |
| db: &dyn ExpandDatabase, |
| macro_call_id: MacroCallId, |
| ) -> Option<ExpandResult<Arc<[SyntaxError]>>> { |
| let e: ExpandResult<Arc<[SyntaxError]>> = |
| db.parse_macro_expansion(macro_call_id).as_ref().map(|it| Arc::from(it.0.errors())); |
| if e.value.is_empty() && e.err.is_none() { None } else { Some(e) } |
| } |
| |
| pub(crate) fn parse_with_map( |
| db: &dyn ExpandDatabase, |
| file_id: HirFileId, |
| ) -> (Parse<SyntaxNode>, SpanMap<'_>) { |
| match file_id { |
| 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) = &db.parse_macro_expansion(macro_file).value; |
| (parse.clone(), SpanMap::ExpansionSpanMap(map)) |
| } |
| } |
| } |
| |
| /// 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 |
| #[allow(deprecated)] // we are macro_arg_considering_derives |
| fn macro_arg_considering_derives<'db>( |
| db: &'db dyn ExpandDatabase, |
| id: MacroCallId, |
| kind: &MacroCallKind, |
| ) -> &'db MacroArgResult { |
| match kind { |
| // Get the macro arg for the derive macro |
| MacroCallKind::Derive { derive_macro_id, .. } => db.macro_arg(*derive_macro_id), |
| // Normal macro arg |
| _ => db.macro_arg(id), |
| } |
| } |
| |
| #[salsa_macros::tracked(returns(ref))] |
| fn macro_arg(db: &dyn ExpandDatabase, id: MacroCallId) -> MacroArgResult { |
| let loc = id.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) = parse_with_map(db, loc.kind.file_id()); |
| 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) |
| } |
| |
| impl<'db> TokenExpander<'db> { |
| fn macro_expander(db: &'db dyn ExpandDatabase, id: MacroDefId) -> TokenExpander<'db> { |
| match id.kind { |
| MacroDefKind::Declarative(ast_id, _) => { |
| TokenExpander::DeclarativeMacro(db.decl_macro_expander(id.krate, ast_id)) |
| } |
| MacroDefKind::BuiltIn(_, expander) => TokenExpander::BuiltIn(expander), |
| MacroDefKind::BuiltInAttr(_, expander) => TokenExpander::BuiltInAttr(expander), |
| MacroDefKind::BuiltInDerive(_, expander) => TokenExpander::BuiltInDerive(expander), |
| MacroDefKind::BuiltInEager(_, expander) => TokenExpander::BuiltInEager(expander), |
| MacroDefKind::ProcMacro(_, expander, _) => TokenExpander::ProcMacro(expander), |
| MacroDefKind::UnimplementedBuiltIn(_) => TokenExpander::UnimplementedBuiltIn, |
| } |
| } |
| } |
| |
| fn macro_expand<'db>( |
| db: &'db dyn ExpandDatabase, |
| macro_call_id: MacroCallId, |
| 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 db |
| .expand_proc_macro(macro_call_id) |
| .as_ref() |
| .map(|it| (Cow::Borrowed(it), None)); |
| } |
| _ => { |
| let (macro_arg, undo_info, span) = |
| db.macro_arg_considering_derives(macro_call_id, &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.clone(), macro_call_id, span), |
| MacroDefKind::BuiltIn(_, it) => { |
| it.expand(db, macro_call_id, arg, span).map_err(Into::into).zip_val(None) |
| } |
| MacroDefKind::BuiltInDerive(_, it) => { |
| it.expand(db, macro_call_id, 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, macro_call_id, 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, macro_call_id, arg, span); |
| fixup::reverse_fixups(&mut res.value, undo_info); |
| res.zip_val(None) |
| } |
| MacroDefKind::ProcMacro(_, _, _) => unreachable!(), |
| }; |
| (ExpandResult { value: res.value, err: res.err }, 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 } |
| } |
| |
| fn proc_macro_span(db: &dyn ExpandDatabase, ast: AstId<ast::Fn>) -> Span { |
| let root = db.parse_or_expand(ast.file_id); |
| let ast_id_map = &db.ast_id_map(ast.file_id); |
| 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) |
| } |
| |
| #[salsa_macros::tracked(returns(ref))] |
| fn expand_proc_macro(db: &dyn ExpandDatabase, id: MacroCallId) -> ExpandResult<tt::TopSubtree> { |
| let loc = id.loc(db); |
| let (macro_arg, undo_info, span) = db.macro_arg_considering_derives(id, &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 = db.proc_macro_span(ast); |
| expander.expand( |
| db, |
| loc.def.krate, |
| loc.krate, |
| macro_arg, |
| attr_arg, |
| span_with_def_site_ctxt(db, span, id.into(), loc.def.edition), |
| span_with_call_site_ctxt(db, span, id.into(), loc.def.edition), |
| span_with_mixed_site_ctxt(db, span, id.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 } |
| } |
| |
| 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}", |
| ), |
| )), |
| }) |
| } |
| } |