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