Skip to main content

cairo_lang_semantic/items/
macro_declaration.rs

1use std::sync::Arc;
2
3use cairo_lang_defs::db::DefsGroup;
4use cairo_lang_defs::ids::{
5    LanguageElementId, LookupItemId, MacroDeclarationId, ModuleId, ModuleItemId,
6};
7use cairo_lang_diagnostics::{Diagnostics, Maybe, skip_diagnostic};
8use cairo_lang_filesystem::db::FilesGroup;
9use cairo_lang_filesystem::ids::{CodeMapping, CodeOrigin, SmolStrId};
10use cairo_lang_filesystem::span::{TextSpan, TextWidth};
11use cairo_lang_parser::macro_helpers::as_expr_macro_token_tree;
12use cairo_lang_syntax::attribute::structured::{Attribute, AttributeListStructurize};
13use cairo_lang_syntax::node::ast::{MacroElement, MacroParam};
14use cairo_lang_syntax::node::ids::SyntaxStablePtrId;
15use cairo_lang_syntax::node::kind::SyntaxKind;
16use cairo_lang_syntax::node::{SyntaxNode, Terminal, TypedStablePtr, TypedSyntaxNode, ast};
17use cairo_lang_utils::ordered_hash_map::OrderedHashMap;
18use salsa::Database;
19
20use crate::SemanticDiagnostic;
21use crate::diagnostic::{SemanticDiagnosticKind, SemanticDiagnostics, SemanticDiagnosticsBuilder};
22use crate::expr::inference::InferenceId;
23use crate::keyword::{MACRO_CALL_SITE, MACRO_DEF_SITE};
24use crate::resolve::{Resolver, ResolverData};
25
26/// A unique identifier for a repetition block inside a macro rule.
27/// Each `$( ... )` group in the macro pattern gets a new `RepetitionId`.
28#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
29pub struct RepetitionId(usize);
30
31/// The captures collected during macro pattern matching.
32/// Each macro parameter name maps to a flat list of matched strings.
33type Captures<'db> = OrderedHashMap<SmolStrId<'db>, Vec<CapturedValue<'db>>>;
34
35/// Context used during macro pattern matching and expansion.
36/// Tracks captured values, active repetition scopes, and repetition ownership per placeholder.
37#[derive(Default, Clone, Debug)]
38pub struct MatcherContext<'db> {
39    /// The captured values per macro parameter name.
40    /// These are flat lists, even for repeated placeholders.
41    pub captures: Captures<'db>,
42
43    /// Maps each placeholder to the `RepetitionId` of the repetition block
44    /// they are part of. This helps the expansion phase know which iterators to advance together.
45    pub placeholder_to_rep_id: OrderedHashMap<SmolStrId<'db>, RepetitionId>,
46
47    /// Stack of currently active repetition blocks. Used to assign placeholders
48    /// to their correct `RepetitionId` while recursing into nested repetitions.
49    pub current_repetition_stack: Vec<RepetitionId>,
50
51    /// Counter for generating unique `RepetitionId`s.
52    pub next_repetition_id: usize,
53
54    /// Tracks the current index for each active repetition during expansion.
55    pub repetition_indices: OrderedHashMap<RepetitionId, usize>,
56
57    /// Count how many times each repetition matched.
58    pub repetition_match_counts: OrderedHashMap<RepetitionId, usize>,
59
60    /// Store the repetition operator for each repetition.
61    pub repetition_operators: OrderedHashMap<RepetitionId, ast::MacroRepetitionOperator<'db>>,
62}
63
64/// The semantic data for a macro declaration.
65#[derive(Debug, Clone, PartialEq, Eq, salsa::Update)]
66pub struct MacroDeclarationData<'db> {
67    rules: Vec<MacroRuleData<'db>>,
68    attributes: Vec<Attribute<'db>>,
69    diagnostics: Diagnostics<'db, SemanticDiagnostic<'db>>,
70    resolver_data: Arc<ResolverData<'db>>,
71}
72
73/// The semantic data for a single macro rule in a macro declaration.
74#[derive(Debug, Clone, PartialEq, Eq, salsa::Update)]
75pub struct MacroRuleData<'db> {
76    pub pattern: ast::WrappedMacro<'db>,
77    pub expansion: ast::MacroElements<'db>,
78    /// Set to `Err` when this rule has semantic errors (e.g., undefined placeholders).
79    /// Callers must skip expansion when this is `Err`.
80    pub err: Maybe<()>,
81}
82
83/// The possible kinds of placeholders in a macro rule.
84#[derive(Debug, Clone, PartialEq, Eq)]
85enum PlaceholderKind {
86    Identifier,
87    Expr,
88}
89
90impl<'db> From<ast::MacroParamKind<'db>> for PlaceholderKind {
91    fn from(kind: ast::MacroParamKind<'db>) -> Self {
92        match kind {
93            ast::MacroParamKind::Identifier(_) => PlaceholderKind::Identifier,
94            ast::MacroParamKind::Expr(_) => PlaceholderKind::Expr,
95            ast::MacroParamKind::Missing(_) => unreachable!(
96                "Missing macro rule param kind, should have been handled by the parser."
97            ),
98        }
99    }
100}
101
102/// Information about a captured value in a macro.
103#[derive(Clone, Debug, PartialEq, Eq)]
104pub struct CapturedValue<'db> {
105    pub text: String,
106    pub stable_ptr: SyntaxStablePtrId<'db>,
107}
108
109/// Implementation of [MacroDeclarationSemantic::priv_macro_declaration_data].
110fn priv_macro_declaration_data<'db>(
111    db: &'db dyn Database,
112    macro_declaration_id: MacroDeclarationId<'db>,
113) -> Maybe<MacroDeclarationData<'db>> {
114    let module_id = macro_declaration_id.parent_module(db);
115    let mut diagnostics = SemanticDiagnostics::new(module_id);
116
117    let macro_declaration_syntax = db.module_macro_declaration_by_id(macro_declaration_id)?;
118    if !are_user_defined_inline_macros_enabled(db, module_id) {
119        diagnostics.report(
120            macro_declaration_syntax.stable_ptr(db).untyped(),
121            SemanticDiagnosticKind::UserDefinedInlineMacrosDisabled,
122        );
123    }
124
125    let attributes = macro_declaration_syntax.attributes(db).structurize(db);
126    let inference_id = InferenceId::LookupItemDeclaration(LookupItemId::ModuleItem(
127        ModuleItemId::MacroDeclaration(macro_declaration_id),
128    ));
129    let resolver = Resolver::new(db, module_id, inference_id);
130
131    // TODO(Dean): Verify uniqueness of param names.
132    // TODO(Dean): Verify consistency bracket terminals.
133    let mut rules = vec![];
134    for rule_syntax in macro_declaration_syntax.rules(db).elements(db) {
135        let pattern = rule_syntax.lhs(db);
136        let expansion = rule_syntax.rhs(db).elements(db);
137        let pattern_elements = get_macro_elements(db, pattern.clone());
138        // Collect the repetition path (outermost-to-innermost pattern rep IDs) for every
139        // placeholder defined in the pattern.
140        let mut placeholder_paths: OrderedHashMap<SmolStrId<'db>, Vec<usize>> = Default::default();
141        let mut next_rep_id = 0;
142        collect_placeholder_paths(
143            db,
144            pattern_elements.elements(db),
145            &mut vec![],
146            &mut next_rep_id,
147            &mut placeholder_paths,
148        );
149
150        let mut ctx = ExpansionCheckCtx {
151            db,
152            known_path: &[],
153            curr_rep_depth: 0,
154            placeholder_paths: &placeholder_paths,
155            diagnostics: &mut diagnostics,
156            rule_err: Ok(()),
157        };
158        ctx.check_node(expansion.as_syntax_node());
159        rules.push(MacroRuleData { pattern, expansion, err: ctx.rule_err });
160    }
161    let resolver_data = Arc::new(resolver.data);
162    Ok(MacroDeclarationData { diagnostics: diagnostics.build(), attributes, resolver_data, rules })
163}
164
165/// Query implementation of [MacroDeclarationSemantic::priv_macro_declaration_data].
166#[salsa::tracked]
167fn priv_macro_declaration_data_tracked<'db>(
168    db: &'db dyn Database,
169    macro_declaration_id: MacroDeclarationId<'db>,
170) -> Maybe<MacroDeclarationData<'db>> {
171    priv_macro_declaration_data(db, macro_declaration_id)
172}
173
174/// Helper function to extract pattern elements from a WrappedMacro.
175fn get_macro_elements<'db>(
176    db: &'db dyn Database,
177    pattern: ast::WrappedMacro<'db>,
178) -> ast::MacroElements<'db> {
179    match pattern {
180        ast::WrappedMacro::Parenthesized(inner) => inner.elements(db),
181        ast::WrappedMacro::Braced(inner) => inner.elements(db),
182        ast::WrappedMacro::Bracketed(inner) => inner.elements(db),
183    }
184}
185
186/// Helper function to extract a placeholder name from an ExprPath node, if it represents a macro
187/// placeholder. Returns None if the path is not a valid macro placeholder.
188fn extract_placeholder<'db>(
189    db: &'db dyn Database,
190    path_node: &MacroParam<'db>,
191) -> Option<SmolStrId<'db>> {
192    let placeholder_name = path_node.name(db).as_syntax_node().get_text_without_trivia(db);
193    if ![MACRO_DEF_SITE, MACRO_CALL_SITE].contains(&placeholder_name.long(db).as_str()) {
194        return Some(placeholder_name);
195    }
196    None
197}
198
199/// Assigns a unique ID to every `$()` repetition block in the pattern (left-to-right DFS order)
200/// and records, for each placeholder, its path: the ordered list of ancestor repetition IDs from
201/// outermost to innermost. The resulting map is used by [`ExpansionCheckCtx`] to validate the
202/// expansion.
203fn collect_placeholder_paths<'db>(
204    db: &'db dyn Database,
205    elements: impl IntoIterator<Item = ast::MacroElement<'db>>,
206    current_path: &mut Vec<usize>,
207    next_rep_id: &mut usize,
208    result: &mut OrderedHashMap<SmolStrId<'db>, Vec<usize>>,
209) {
210    for element in elements {
211        match element {
212            ast::MacroElement::Param(param) => {
213                result.insert(
214                    param.name(db).as_syntax_node().get_text_without_trivia(db),
215                    current_path.clone(),
216                );
217            }
218            ast::MacroElement::Repetition(rep) => {
219                let rep_id = *next_rep_id;
220                *next_rep_id += 1;
221                current_path.push(rep_id);
222                let inner = rep.elements(db).elements(db);
223                collect_placeholder_paths(db, inner, current_path, next_rep_id, result);
224                assert_eq!(current_path.pop(), Some(rep_id));
225            }
226            ast::MacroElement::Subtree(subtree) => {
227                let inner = get_macro_elements(db, subtree.subtree(db)).elements(db);
228                collect_placeholder_paths(db, inner, current_path, next_rep_id, result);
229            }
230            ast::MacroElement::Token(_) => {}
231        }
232    }
233}
234
235/// Context for validating placeholder usage in a macro rule's expansion.
236struct ExpansionCheckCtx<'db, 'a> {
237    db: &'db dyn Database,
238    /// Maps each placeholder name to its pattern path: the sequence of repetition IDs
239    /// (outermost to innermost) of the `$()` blocks it is nested in within the pattern.
240    placeholder_paths: &'a OrderedHashMap<SmolStrId<'db>, Vec<usize>>,
241    /// Number of `$()` expansion blocks currently entered. Used for E2198 depth checks
242    /// and to trim `known_path` when exiting a block.
243    curr_rep_depth: usize,
244    /// The deepest placeholder path seen so far within the current expansion scope.
245    /// New placeholders at the same depth are validated against this prefix (E2199).
246    /// Invariant: `known_path.len() <= curr_rep_depth`.
247    /// Trimmed to `curr_rep_depth` on `$()` exit so sibling blocks start fresh.
248    known_path: &'a [usize],
249    diagnostics: &'a mut SemanticDiagnostics<'db>,
250    /// `Err` if any diagnostic has been emitted; callers skip expansion when set.
251    rule_err: Maybe<()>,
252}
253
254impl<'db> ExpansionCheckCtx<'db, '_> {
255    /// Validates placeholder usage by recursively traversing `node`.
256    ///
257    /// Two kinds of errors are reported:
258    /// * Depth mismatch (E2198): placeholder used at fewer expansion levels than its pattern depth.
259    /// * Context mismatch (E2199): placeholder from a different repetition than the driving one.
260    fn check_node(&mut self, node: SyntaxNode<'db>) {
261        let db = self.db;
262        if let Some(param) = MacroParam::cast(db, node) {
263            if let Some(name) = extract_placeholder(db, &param) {
264                let ptr = param.stable_ptr(db).untyped();
265                match self.placeholder_paths.get(&name) {
266                    None => {
267                        self.rule_err = Err(self
268                            .diagnostics
269                            .report(ptr, SemanticDiagnosticKind::UndefinedMacroPlaceholder(name)));
270                    }
271                    Some(path) => {
272                        if path.len() > self.curr_rep_depth {
273                            self.rule_err = Err(self.diagnostics.report(
274                                ptr,
275                                SemanticDiagnosticKind::MacroPlaceholderRepDepthMismatch {
276                                    name,
277                                    required: path.len(),
278                                    actual: self.curr_rep_depth,
279                                },
280                            ));
281                        } else {
282                            let cmp_size = path.len().min(self.known_path.len());
283                            if path[..cmp_size] != self.known_path[..cmp_size] {
284                                self.rule_err = Err(self.diagnostics.report(
285                                    ptr,
286                                    SemanticDiagnosticKind::MacroPlaceholderRepDriverMismatch(name),
287                                ));
288                            } else if path.len() > self.known_path.len() {
289                                self.known_path = path;
290                            }
291                        }
292                    }
293                }
294            }
295            return;
296        }
297
298        if let Some(repetition) = ast::MacroRepetition::cast(db, node) {
299            self.curr_rep_depth += 1;
300            for element in repetition.elements(db).elements(db) {
301                self.check_node(element.as_syntax_node());
302            }
303            self.curr_rep_depth -= 1;
304            if self.curr_rep_depth < self.known_path.len() {
305                // Trimming `self.known_path` so it won't leak between different repetitions.
306                self.known_path = &self.known_path[..self.curr_rep_depth];
307            }
308        } else if !node.kind(db).is_terminal() {
309            for child in node.get_children(db).iter() {
310                self.check_node(*child);
311            }
312        }
313    }
314}
315
316/// Given a macro declaration and an input token tree, checks if the input the given rule, and
317/// returns the captured params if it does.
318pub fn is_macro_rule_match<'db>(
319    db: &'db dyn Database,
320    rule: &MacroRuleData<'db>,
321    input: &ast::TokenTreeNode<'db>,
322) -> Option<(Captures<'db>, OrderedHashMap<SmolStrId<'db>, RepetitionId>)> {
323    let mut ctx = MatcherContext::default();
324
325    let matcher_elements = get_macro_elements(db, rule.pattern.clone());
326    let mut input_iter = match input.subtree(db) {
327        ast::WrappedTokenTree::Parenthesized(tt) => tt.tokens(db),
328        ast::WrappedTokenTree::Braced(tt) => tt.tokens(db),
329        ast::WrappedTokenTree::Bracketed(tt) => tt.tokens(db),
330        ast::WrappedTokenTree::Missing(_) => unreachable!(),
331    }
332    .elements(db)
333    .peekable();
334    is_macro_rule_match_ex(db, matcher_elements, &mut input_iter, &mut ctx, true)?;
335    if !validate_repetition_operator_constraints(&ctx) {
336        return None;
337    }
338    Some((ctx.captures, ctx.placeholder_to_rep_id))
339}
340
341/// Helper function for [expand_macro_rule].
342/// Traverses the macro expansion and replaces the placeholders with the provided values,
343/// while collecting the result in `res_buffer`.
344/// Returns `Some(true)` if the match succeeded and some input was consumed,
345/// `Some(false)` if the match succeeded but no input was consumed (empty match),
346/// and `None` if the match failed.
347fn is_macro_rule_match_ex<'db>(
348    db: &'db dyn Database,
349    matcher_elements: ast::MacroElements<'db>,
350    input_iter: &mut std::iter::Peekable<
351        impl DoubleEndedIterator<Item = ast::TokenTree<'db>> + Clone,
352    >,
353    ctx: &mut MatcherContext<'db>,
354    consume_all_input: bool,
355) -> Option<bool> {
356    let mut advanced = false;
357    for matcher_element in matcher_elements.elements(db) {
358        match matcher_element {
359            ast::MacroElement::Token(matcher_token) => {
360                advanced = true;
361                let input_token = input_iter.next()?;
362                match input_token {
363                    ast::TokenTree::Token(token_tree_leaf) => {
364                        if matcher_token.as_syntax_node().get_text_without_trivia(db)
365                            != token_tree_leaf.as_syntax_node().get_text_without_trivia(db)
366                        {
367                            return None;
368                        }
369                        continue;
370                    }
371                    ast::TokenTree::Subtree(_) => return None,
372                    ast::TokenTree::Repetition(_) => return None,
373                    ast::TokenTree::Param(_) => return None,
374                    ast::TokenTree::Missing(_) => unreachable!(),
375                }
376            }
377            ast::MacroElement::Param(param) => {
378                advanced = true;
379                let placeholder_kind: PlaceholderKind =
380                    if let ast::OptionParamKind::ParamKind(param_kind) = param.kind(db) {
381                        param_kind.kind(db).into()
382                    } else {
383                        return None;
384                    };
385                let placeholder_name = param.name(db).as_syntax_node().get_text_without_trivia(db);
386                match placeholder_kind {
387                    PlaceholderKind::Identifier => {
388                        let input_token = input_iter.next()?;
389                        let captured_text = match &input_token {
390                            ast::TokenTree::Token(token_tree_leaf) => {
391                                match token_tree_leaf.leaf(db) {
392                                    ast::TokenNode::TerminalIdentifier(terminal_identifier) => {
393                                        terminal_identifier.text(db).to_string(db)
394                                    }
395                                    _ => return None,
396                                }
397                            }
398                            _ => return None,
399                        };
400                        ctx.captures.entry(placeholder_name).or_default().push(CapturedValue {
401                            text: captured_text,
402                            stable_ptr: input_token.stable_ptr(db).untyped(),
403                        });
404                        if let Some(rep_id) = ctx.current_repetition_stack.last() {
405                            ctx.placeholder_to_rep_id.insert(placeholder_name, *rep_id);
406                        }
407                        continue;
408                    }
409                    PlaceholderKind::Expr => {
410                        let peek_token = input_iter.peek().cloned()?;
411                        let file_id = peek_token.as_syntax_node().stable_ptr(db).file_id(db);
412                        let expr_node = as_expr_macro_token_tree(input_iter.clone(), file_id, db)?;
413                        let expr_text = expr_node.as_syntax_node().get_text(db);
414                        let expr_length = expr_text.len();
415                        // An empty expression is parsed successfully. However we don't want to
416                        // capture it a valid expr.
417                        if expr_length == 0 {
418                            return None;
419                        }
420
421                        ctx.captures.entry(placeholder_name).or_default().push(CapturedValue {
422                            text: expr_text.to_string(),
423                            stable_ptr: peek_token.stable_ptr(db).untyped(),
424                        });
425                        if let Some(rep_id) = ctx.current_repetition_stack.last() {
426                            ctx.placeholder_to_rep_id.insert(placeholder_name, *rep_id);
427                        }
428                        let expr_length = expr_text.len();
429                        let mut current_length = 0;
430
431                        // TODO(Dean): Use the iterator directly in the parser and advance it while
432                        // parsing the expression, instead of manually tracking the length and
433                        // iterating separately.
434                        for token_tree_leaf in input_iter.by_ref() {
435                            let token_text = match token_tree_leaf {
436                                ast::TokenTree::Token(leaf) => leaf.as_syntax_node(),
437                                ast::TokenTree::Subtree(subtree) => subtree.as_syntax_node(),
438                                ast::TokenTree::Repetition(rep) => rep.as_syntax_node(),
439                                ast::TokenTree::Param(param) => param.as_syntax_node(),
440                                ast::TokenTree::Missing(_) => unreachable!(),
441                            }
442                            .get_text(db);
443                            current_length += token_text.len();
444                            if current_length >= expr_length {
445                                break;
446                            }
447                        }
448                        continue;
449                    }
450                }
451            }
452            ast::MacroElement::Subtree(matcher_subtree) => {
453                advanced = true;
454                let input_token = input_iter.next()?;
455                if let ast::TokenTree::Subtree(input_subtree) = input_token {
456                    let inner_elements = get_macro_elements(db, matcher_subtree.subtree(db));
457                    let mut inner_input_iter = match input_subtree.subtree(db) {
458                        ast::WrappedTokenTree::Parenthesized(tt) => tt.tokens(db),
459                        ast::WrappedTokenTree::Braced(tt) => tt.tokens(db),
460                        ast::WrappedTokenTree::Bracketed(tt) => tt.tokens(db),
461                        ast::WrappedTokenTree::Missing(_) => unreachable!(),
462                    }
463                    .elements(db)
464                    .peekable();
465                    is_macro_rule_match_ex(db, inner_elements, &mut inner_input_iter, ctx, true)?;
466                    continue;
467                } else {
468                    return None;
469                }
470            }
471            ast::MacroElement::Repetition(repetition) => {
472                let rep_id = RepetitionId(ctx.next_repetition_id);
473                ctx.next_repetition_id += 1;
474                ctx.current_repetition_stack.push(rep_id);
475                let elements = repetition.elements(db);
476                let operator = repetition.operator(db);
477                let separator_token = repetition.separator(db);
478                let expected_separator = match separator_token {
479                    ast::OptionTerminalComma::TerminalComma(sep) => {
480                        Some(sep.as_syntax_node().get_text_without_trivia(db))
481                    }
482                    ast::OptionTerminalComma::Empty(_) => None,
483                };
484                let mut match_count = 0;
485                loop {
486                    let mut inner_ctx = ctx.clone();
487                    let mut temp_iter = input_iter.clone();
488                    let Some(true) = is_macro_rule_match_ex(
489                        db,
490                        elements.clone(),
491                        &mut temp_iter,
492                        &mut inner_ctx,
493                        false,
494                    ) else {
495                        break;
496                    };
497                    advanced = true;
498                    *ctx = inner_ctx;
499                    *input_iter = temp_iter;
500                    match_count += 1;
501                    if let Some(expected_sep) = &expected_separator {
502                        if let Some(ast::TokenTree::Token(token_leaf)) = input_iter.peek() {
503                            let actual = token_leaf.as_syntax_node().get_text_without_trivia(db);
504                            if actual == *expected_sep {
505                                input_iter.next();
506                            } else {
507                                break;
508                            }
509                        } else {
510                            break;
511                        }
512                    }
513                }
514                ctx.repetition_match_counts.insert(rep_id, match_count);
515                ctx.repetition_operators.insert(rep_id, operator.clone());
516                for placeholder_name in ctx.captures.keys() {
517                    ctx.placeholder_to_rep_id.insert(*placeholder_name, rep_id);
518                }
519
520                for i in 0..match_count {
521                    ctx.repetition_indices.insert(rep_id, i);
522                }
523                ctx.current_repetition_stack.pop();
524                continue;
525            }
526        }
527    }
528
529    if consume_all_input && input_iter.next().is_some() {
530        return None;
531    }
532    Some(advanced)
533}
534
535fn validate_repetition_operator_constraints(ctx: &MatcherContext<'_>) -> bool {
536    for (&rep_id, &count) in ctx.repetition_match_counts.iter() {
537        match ctx.repetition_operators.get(&rep_id) {
538            Some(ast::MacroRepetitionOperator::ZeroOrOne(_)) if count > 1 => return false,
539            Some(ast::MacroRepetitionOperator::OneOrMore(_)) if count < 1 => return false,
540            Some(ast::MacroRepetitionOperator::ZeroOrMore(_)) | None => {}
541            _ => {}
542        }
543    }
544    true
545}
546
547/// The result of expanding a macro rule.
548#[derive(Debug, Clone, PartialEq, Eq)]
549pub struct MacroExpansionResult {
550    /// The expanded text.
551    pub text: Arc<str>,
552    /// Information about placeholder expansions in this macro expansion.
553    pub code_mappings: Arc<[CodeMapping]>,
554}
555
556/// Traverse the macro expansion and replace the placeholders with the provided values, creates a
557/// string representation of the expanded macro.
558///
559/// Returns an error if any used placeholder in the expansion is not found in the captures.
560/// When an error is returned, appropriate diagnostics will already have been reported.
561pub fn expand_macro_rule(
562    db: &dyn Database,
563    rule: &MacroRuleData<'_>,
564    matcher_ctx: &mut MatcherContext<'_>,
565) -> Maybe<MacroExpansionResult> {
566    let node = rule.expansion.as_syntax_node();
567    let mut res_buffer = String::new();
568    let mut code_mappings = Vec::new();
569    expand_macro_rule_ex(db, node, matcher_ctx, &mut res_buffer, &mut code_mappings)?;
570    Ok(MacroExpansionResult { text: res_buffer.into(), code_mappings: code_mappings.into() })
571}
572
573/// Helper function for [expand_macro_rule]. Traverses the macro expansion and replaces the
574/// placeholders with the provided values while collecting the result in res_buffer.
575///
576/// Returns an error if a placeholder is not found in captures.
577/// When an error is returned, appropriate diagnostics will already have been reported.
578fn expand_macro_rule_ex(
579    db: &dyn Database,
580    node: SyntaxNode<'_>,
581    matcher_ctx: &mut MatcherContext<'_>,
582    res_buffer: &mut String,
583    code_mappings: &mut Vec<CodeMapping>,
584) -> Maybe<()> {
585    match node.kind(db) {
586        SyntaxKind::MacroParam => {
587            let path_node = MacroParam::from_syntax_node(db, node);
588            if let Some(name) = extract_placeholder(db, &path_node) {
589                let rep_index = matcher_ctx
590                    .placeholder_to_rep_id
591                    .get(&name)
592                    .and_then(|rep_id| matcher_ctx.repetition_indices.get(rep_id))
593                    .copied();
594                let value = matcher_ctx
595                    .captures
596                    .get(&name)
597                    .and_then(|v| rep_index.map_or_else(|| v.first(), |i| v.get(i)))
598                    .ok_or_else(skip_diagnostic)?;
599                let start = TextWidth::from_str(res_buffer).as_offset();
600                let span = TextSpan::new_with_width(start, TextWidth::from_str(&value.text));
601                res_buffer.push_str(&value.text);
602                code_mappings.push(CodeMapping {
603                    span,
604                    origin: CodeOrigin::Span(value.stable_ptr.lookup(db).span_without_trivia(db)),
605                });
606                return Ok(());
607            }
608        }
609        SyntaxKind::MacroRepetition => {
610            let repetition = ast::MacroRepetition::from_syntax_node(db, node);
611            let elements = repetition.elements(db);
612            let first_param = find_first_repetition_param(db, elements.elements(db))
613                .ok_or_else(skip_diagnostic)?;
614            let placeholder_name = first_param.name(db).text(db);
615            // If the placeholder isn't mapped to any repetition, it means it doesn't belong to any
616            // consumed repetition.
617            let Some(rep_id) = matcher_ctx.placeholder_to_rep_id.get(&placeholder_name).copied()
618            else {
619                return Ok(());
620            };
621            let repetition_len =
622                matcher_ctx.captures.get(&placeholder_name).map(|v| v.len()).unwrap_or(0);
623            for i in 0..repetition_len {
624                matcher_ctx.repetition_indices.insert(rep_id, i);
625                for element in elements.elements(db) {
626                    expand_macro_rule_ex(
627                        db,
628                        element.as_syntax_node(),
629                        matcher_ctx,
630                        res_buffer,
631                        code_mappings,
632                    )?;
633                }
634
635                if i + 1 < repetition_len
636                    && let ast::OptionTerminalComma::TerminalComma(sep) = repetition.separator(db)
637                {
638                    res_buffer.push_str(sep.as_syntax_node().get_text(db));
639                }
640            }
641
642            matcher_ctx.repetition_indices.swap_remove(&rep_id);
643            return Ok(());
644        }
645        _ => {
646            if node.kind(db).is_terminal() {
647                res_buffer.push_str(node.get_text(db));
648                return Ok(());
649            }
650
651            for child in node.get_children(db).iter() {
652                expand_macro_rule_ex(db, *child, matcher_ctx, res_buffer, code_mappings)?;
653            }
654            return Ok(());
655        }
656    }
657    if node.kind(db).is_terminal() {
658        res_buffer.push_str(node.get_text(db));
659        return Ok(());
660    }
661    for child in node.get_children(db).iter() {
662        expand_macro_rule_ex(db, *child, matcher_ctx, res_buffer, code_mappings)?;
663    }
664    Ok(())
665}
666
667/// Returns the first param within the given macro elements.
668fn find_first_repetition_param<'db>(
669    db: &'db dyn Database,
670    elements: impl IntoIterator<Item = MacroElement<'db>>,
671) -> Option<MacroParam<'db>> {
672    for element in elements {
673        match element {
674            ast::MacroElement::Param(param) => return Some(param),
675            ast::MacroElement::Subtree(subtree) => {
676                let inner_elements = get_macro_elements(db, subtree.subtree(db)).elements(db);
677                if let Some(param) = find_first_repetition_param(db, inner_elements) {
678                    return Some(param);
679                }
680            }
681            ast::MacroElement::Repetition(repetition) => {
682                let inner_elements = repetition.elements(db).elements(db);
683                if let Some(param) = find_first_repetition_param(db, inner_elements) {
684                    return Some(param);
685                }
686            }
687            ast::MacroElement::Token(_) => {}
688        }
689    }
690    None
691}
692
693/// Implementation of [MacroDeclarationSemantic::macro_declaration_diagnostics].
694fn macro_declaration_diagnostics<'db>(
695    db: &'db dyn Database,
696    macro_declaration_id: MacroDeclarationId<'db>,
697) -> Diagnostics<'db, SemanticDiagnostic<'db>> {
698    priv_macro_declaration_data(db, macro_declaration_id)
699        .map(|data| data.diagnostics)
700        .unwrap_or_default()
701}
702
703/// Query implementation of [MacroDeclarationSemantic::macro_declaration_diagnostics].
704#[salsa::tracked]
705fn macro_declaration_diagnostics_tracked<'db>(
706    db: &'db dyn Database,
707    macro_declaration_id: MacroDeclarationId<'db>,
708) -> Diagnostics<'db, SemanticDiagnostic<'db>> {
709    macro_declaration_diagnostics(db, macro_declaration_id)
710}
711
712/// Implementation of [MacroDeclarationSemantic::macro_declaration_attributes].
713fn macro_declaration_attributes<'db>(
714    db: &'db dyn Database,
715    macro_declaration_id: MacroDeclarationId<'db>,
716) -> Maybe<Vec<Attribute<'db>>> {
717    priv_macro_declaration_data(db, macro_declaration_id).map(|data| data.attributes)
718}
719
720/// Query implementation of [MacroDeclarationSemantic::macro_declaration_attributes].
721#[salsa::tracked]
722fn macro_declaration_attributes_tracked<'db>(
723    db: &'db dyn Database,
724    macro_declaration_id: MacroDeclarationId<'db>,
725) -> Maybe<Vec<Attribute<'db>>> {
726    macro_declaration_attributes(db, macro_declaration_id)
727}
728
729/// Implementation of [MacroDeclarationSemantic::macro_declaration_resolver_data].
730fn macro_declaration_resolver_data<'db>(
731    db: &'db dyn Database,
732    macro_declaration_id: MacroDeclarationId<'db>,
733) -> Maybe<Arc<ResolverData<'db>>> {
734    priv_macro_declaration_data(db, macro_declaration_id).map(|data| data.resolver_data)
735}
736
737/// Query implementation of [MacroDeclarationSemantic::macro_declaration_resolver_data].
738#[salsa::tracked]
739fn macro_declaration_resolver_data_tracked<'db>(
740    db: &'db dyn Database,
741    macro_declaration_id: MacroDeclarationId<'db>,
742) -> Maybe<Arc<ResolverData<'db>>> {
743    macro_declaration_resolver_data(db, macro_declaration_id)
744}
745
746/// Implementation of [MacroDeclarationSemantic::macro_declaration_rules].
747fn macro_declaration_rules<'db>(
748    db: &'db dyn Database,
749    macro_declaration_id: MacroDeclarationId<'db>,
750) -> Maybe<Vec<MacroRuleData<'db>>> {
751    priv_macro_declaration_data(db, macro_declaration_id).map(|data| data.rules)
752}
753
754/// Query implementation of [MacroDeclarationSemantic::macro_declaration_rules].
755#[salsa::tracked]
756fn macro_declaration_rules_tracked<'db>(
757    db: &'db dyn Database,
758    macro_declaration_id: MacroDeclarationId<'db>,
759) -> Maybe<Vec<MacroRuleData<'db>>> {
760    macro_declaration_rules(db, macro_declaration_id)
761}
762
763/// Returns true if user defined user macros are enabled for the given module.
764fn are_user_defined_inline_macros_enabled<'db>(
765    db: &dyn Database,
766    module_id: ModuleId<'db>,
767) -> bool {
768    let owning_crate = module_id.owning_crate(db);
769    let Some(config) = db.crate_config(owning_crate) else { return false };
770    config.settings.experimental_features.user_defined_inline_macros
771}
772
773/// Trait for macro declaration-related semantic queries.
774pub trait MacroDeclarationSemantic<'db>: Database {
775    /// Private query to compute data about a macro declaration.
776    fn priv_macro_declaration_data(
777        &'db self,
778        macro_id: MacroDeclarationId<'db>,
779    ) -> Maybe<MacroDeclarationData<'db>> {
780        priv_macro_declaration_data_tracked(self.as_dyn_database(), macro_id)
781    }
782    /// Returns the semantic diagnostics of a macro declaration.
783    fn macro_declaration_diagnostics(
784        &'db self,
785        macro_id: MacroDeclarationId<'db>,
786    ) -> Diagnostics<'db, SemanticDiagnostic<'db>> {
787        macro_declaration_diagnostics_tracked(self.as_dyn_database(), macro_id)
788    }
789    /// Returns the resolver data of a macro declaration.
790    fn macro_declaration_resolver_data(
791        &'db self,
792        macro_id: MacroDeclarationId<'db>,
793    ) -> Maybe<Arc<ResolverData<'db>>> {
794        macro_declaration_resolver_data_tracked(self.as_dyn_database(), macro_id)
795    }
796    /// Returns the attributes of a macro declaration.
797    fn macro_declaration_attributes(
798        &'db self,
799        macro_id: MacroDeclarationId<'db>,
800    ) -> Maybe<Vec<Attribute<'db>>> {
801        macro_declaration_attributes_tracked(self.as_dyn_database(), macro_id)
802    }
803    /// Returns the rules semantic data of a macro declaration.
804    fn macro_declaration_rules(
805        &'db self,
806        macro_id: MacroDeclarationId<'db>,
807    ) -> Maybe<Vec<MacroRuleData<'db>>> {
808        macro_declaration_rules_tracked(self.as_dyn_database(), macro_id)
809    }
810}
811impl<'db, T: Database + ?Sized> MacroDeclarationSemantic<'db> for T {}