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