Skip to main content

cairo_lang_semantic/items/
macro_call.rs

1use std::sync::Arc;
2
3use cairo_lang_defs::db::DefsGroup;
4use cairo_lang_defs::ids::{LanguageElementId, MacroCallId, ModuleId};
5use cairo_lang_diagnostics::{Diagnostics, Maybe, skip_diagnostic};
6use cairo_lang_filesystem::ids::{
7    CodeMapping, CodeOrigin, FileKind, FileLongId, SmolStrId, VirtualFile,
8};
9use cairo_lang_filesystem::span::{TextOffset, TextSpan};
10use cairo_lang_syntax::node::{TypedStablePtr, TypedSyntaxNode, ast};
11use cairo_lang_utils::Intern;
12use salsa::Database;
13
14use crate::SemanticDiagnostic;
15use crate::diagnostic::{
16    NotFoundItemType, SemanticDiagnosticKind, SemanticDiagnostics, SemanticDiagnosticsBuilder,
17};
18use crate::expr::inference::InferenceId;
19use crate::items::macro_declaration::{
20    MacroDeclarationSemantic, MatcherContext, expand_macro_rule, is_macro_rule_match,
21};
22use crate::items::module::ModuleSemantic;
23use crate::resolve::{ResolutionContext, ResolvedGenericItem, Resolver, ResolverMacroData};
24
25/// The data associated with a macro call in item context.
26#[derive(Debug, Clone, PartialEq, Eq, salsa::Update)]
27pub struct MacroCallData<'db> {
28    /// The module to which the macro call was expanded to.
29    pub macro_call_module: Maybe<ModuleId<'db>>,
30    pub diagnostics: Diagnostics<'db, SemanticDiagnostic<'db>>,
31    pub defsite_module_id: ModuleId<'db>,
32    pub callsite_module_id: ModuleId<'db>,
33    pub expansion_mappings: Arc<[CodeMapping]>,
34    pub parent_macro_call_data: Option<Arc<ResolverMacroData<'db>>>,
35}
36
37/// Implementation of [MacroCallSemantic::priv_macro_call_data].
38fn priv_macro_call_data<'db>(
39    db: &'db dyn Database,
40    macro_call_id: MacroCallId<'db>,
41) -> Maybe<MacroCallData<'db>> {
42    let inference_id = InferenceId::MacroCall(macro_call_id);
43    let module_id = macro_call_id.parent_module(db);
44    let mut resolver = Resolver::new(db, module_id, inference_id);
45    let macro_call_syntax = db.module_macro_call_by_id(macro_call_id)?;
46    // Resolve the macro call path, and report diagnostics if it finds no match or
47    // the resolved item is not a macro declaration.
48    let macro_call_path = macro_call_syntax.path(db);
49    let macro_name = macro_call_path.as_syntax_node().get_text_without_trivia(db);
50    let callsite_module_id = macro_call_id.parent_module(db);
51    // If the call is to `expose!` and no other `expose` item is locally declared - using expose.
52    if macro_name.long(db) == EXPOSE_MACRO_NAME
53        && let Ok(None) = db.module_item_by_name(callsite_module_id, macro_name)
54    {
55        let (content, mapping) = expose_content_and_mapping(db, macro_call_syntax.arguments(db))?;
56        let code_mappings: Arc<[CodeMapping]> = [mapping].into();
57        let generated_file_id = FileLongId::Virtual(VirtualFile {
58            parent: Some(macro_call_syntax.stable_ptr(db).untyped().span_in_file(db)),
59            name: macro_name,
60            content: SmolStrId::from(db, content),
61            code_mappings: code_mappings.clone(),
62            kind: FileKind::Module,
63            original_item_removed: false,
64        })
65        .intern(db);
66        let macro_call_module =
67            ModuleId::MacroCall { id: macro_call_id, generated_file_id, is_expose: true };
68        return Ok(MacroCallData {
69            macro_call_module: Ok(macro_call_module),
70            diagnostics: Default::default(),
71            // Defsite and callsite aren't actually used, as it defines nothing in its code.
72            defsite_module_id: callsite_module_id,
73            callsite_module_id,
74            expansion_mappings: code_mappings,
75            parent_macro_call_data: resolver.macro_call_data,
76        });
77    }
78    let mut diagnostics = SemanticDiagnostics::new(callsite_module_id);
79    let macro_declaration_id = match resolver.resolve_generic_path(
80        &mut diagnostics,
81        &macro_call_path,
82        NotFoundItemType::Macro,
83        ResolutionContext::Default,
84    ) {
85        Ok(ResolvedGenericItem::Macro(macro_declaration_id)) => macro_declaration_id,
86        Ok(_) => {
87            let diag_added = diagnostics.report(
88                macro_call_syntax.stable_ptr(db),
89                SemanticDiagnosticKind::InlineMacroNotFound(macro_name),
90            );
91            return Ok(MacroCallData {
92                macro_call_module: Err(diag_added),
93                diagnostics: diagnostics.build(),
94                defsite_module_id: callsite_module_id,
95                callsite_module_id,
96                expansion_mappings: Arc::new([]),
97                parent_macro_call_data: resolver.macro_call_data,
98            });
99        }
100        Err(diag_added) => {
101            return Ok(MacroCallData {
102                macro_call_module: Err(diag_added),
103                diagnostics: diagnostics.build(),
104                defsite_module_id: callsite_module_id,
105                callsite_module_id,
106                expansion_mappings: Arc::new([]),
107                parent_macro_call_data: resolver.macro_call_data,
108            });
109        }
110    };
111    let defsite_module_id = macro_declaration_id.parent_module(db);
112    let parent_macro_call_data = resolver.macro_call_data;
113    let macro_rules = match db.macro_declaration_rules(macro_declaration_id) {
114        Ok(rules) => rules,
115        Err(diag_added) => {
116            return Ok(MacroCallData {
117                macro_call_module: Err(diag_added),
118                diagnostics: diagnostics.build(),
119                defsite_module_id,
120                callsite_module_id,
121                expansion_mappings: Arc::new([]),
122                parent_macro_call_data,
123            });
124        }
125    };
126    let Some((rule, (captures, placeholder_to_rep_id))) = macro_rules.iter().find_map(|rule| {
127        is_macro_rule_match(db, rule, &macro_call_syntax.arguments(db)).map(|res| (rule, res))
128    }) else {
129        let diag_added = diagnostics.report(
130            macro_call_syntax.stable_ptr(db),
131            SemanticDiagnosticKind::InlineMacroNoMatchingRule(macro_name),
132        );
133        return Ok(MacroCallData {
134            macro_call_module: Err(diag_added),
135            diagnostics: diagnostics.build(),
136            defsite_module_id,
137            callsite_module_id,
138            expansion_mappings: Arc::new([]),
139            parent_macro_call_data,
140        });
141    };
142    // If the rule has declaration-time errors, skip expansion to avoid panics on malformed rules.
143    if let Err(diag_added) = rule.err {
144        return Ok(MacroCallData {
145            macro_call_module: Err(diag_added),
146            diagnostics: diagnostics.build(),
147            defsite_module_id,
148            callsite_module_id,
149            expansion_mappings: Arc::new([]),
150            parent_macro_call_data,
151        });
152    }
153    let mut matcher_ctx = MatcherContext { captures, placeholder_to_rep_id, ..Default::default() };
154    let expanded_code = expand_macro_rule(db, rule, &mut matcher_ctx).unwrap();
155    let generated_file_id = FileLongId::Virtual(VirtualFile {
156        parent: Some(macro_call_syntax.stable_ptr(db).untyped().span_in_file(db)),
157        name: macro_name,
158        content: SmolStrId::from_arcstr(db, &expanded_code.text),
159        code_mappings: expanded_code.code_mappings.clone(),
160        kind: FileKind::Module,
161        original_item_removed: false,
162    })
163    .intern(db);
164    let macro_call_module =
165        ModuleId::MacroCall { id: macro_call_id, generated_file_id, is_expose: false };
166    Ok(MacroCallData {
167        macro_call_module: Ok(macro_call_module),
168        diagnostics: diagnostics.build(),
169        defsite_module_id,
170        callsite_module_id,
171        expansion_mappings: expanded_code.code_mappings,
172        parent_macro_call_data,
173    })
174}
175
176/// Query implementation of [MacroCallSemantic::priv_macro_call_data].
177#[salsa::tracked(cycle_fn=priv_macro_call_data_cycle, cycle_initial=priv_macro_call_data_initial)]
178fn priv_macro_call_data_tracked<'db>(
179    db: &'db dyn Database,
180    macro_call_id: MacroCallId<'db>,
181) -> Maybe<MacroCallData<'db>> {
182    priv_macro_call_data(db, macro_call_id)
183}
184
185/// The name of the `expose!` macro.
186pub const EXPOSE_MACRO_NAME: &str = "expose";
187
188// TODO(eytan-starkware): Return SmolStrId
189/// Gets the content and mappings for the `expose!` macro call.
190pub fn expose_content_and_mapping<'db>(
191    db: &'db dyn Database,
192    args: ast::TokenTreeNode<'db>,
193) -> Maybe<(String, CodeMapping)> {
194    let tokens = match args.subtree(db) {
195        ast::WrappedTokenTree::Parenthesized(tree) => tree.tokens(db),
196        ast::WrappedTokenTree::Braced(tree) => tree.tokens(db),
197        ast::WrappedTokenTree::Bracketed(tree) => tree.tokens(db),
198        ast::WrappedTokenTree::Missing(_) => return Err(skip_diagnostic()),
199    };
200    let tokens_node = tokens.as_syntax_node();
201    let tokens_span = tokens_node.span(db);
202    Ok((
203        tokens_node.get_text(db).to_string(),
204        CodeMapping {
205            span: TextSpan::new_with_width(TextOffset::START, tokens_span.width()),
206            origin: CodeOrigin::Start(tokens_span.start),
207        },
208    ))
209}
210
211/// Cycle handling for [MacroCallSemantic::priv_macro_call_data].
212fn priv_macro_call_data_cycle<'db>(
213    _db: &'db dyn Database,
214    _cycle: &salsa::Cycle<'_>,
215    _last_provisional_value: &Maybe<MacroCallData<'db>>,
216    value: Maybe<MacroCallData<'db>>,
217    _macro_call_id: MacroCallId<'db>,
218) -> Maybe<MacroCallData<'db>> {
219    value
220}
221
222/// Cycle handling for [MacroCallSemantic::priv_macro_call_data].
223fn priv_macro_call_data_initial<'db>(
224    db: &'db dyn Database,
225    _id: salsa::Id,
226    macro_call_id: MacroCallId<'db>,
227) -> Maybe<MacroCallData<'db>> {
228    // If we are in a cycle, we return an empty MacroCallData with no diagnostics.
229    // This is to prevent infinite recursion in case of cyclic macro calls.
230    let module_id = macro_call_id.parent_module(db);
231    let mut diagnostics = SemanticDiagnostics::new(module_id);
232    let macro_call_syntax = db.module_macro_call_by_id(macro_call_id)?;
233    let macro_call_path = macro_call_syntax.path(db);
234    let macro_name = macro_call_path.as_syntax_node().get_text_without_trivia(db);
235
236    let diag_added = diagnostics.report(
237        macro_call_id.stable_ptr(db).untyped(),
238        SemanticDiagnosticKind::InlineMacroNotFound(macro_name),
239    );
240
241    Ok(MacroCallData {
242        macro_call_module: Err(diag_added),
243        diagnostics: diagnostics.build(),
244        defsite_module_id: module_id,
245        callsite_module_id: module_id,
246        expansion_mappings: Arc::new([]),
247        parent_macro_call_data: None,
248    })
249}
250
251/// Implementation of [MacroCallSemantic::macro_call_diagnostics].
252fn macro_call_diagnostics<'db>(
253    db: &'db dyn Database,
254    macro_call_id: MacroCallId<'db>,
255) -> Diagnostics<'db, SemanticDiagnostic<'db>> {
256    db.priv_macro_call_data(macro_call_id).map(|data| data.diagnostics).unwrap_or_default()
257}
258
259/// Query implementation of [MacroCallSemantic::macro_call_diagnostics].
260#[salsa::tracked]
261fn macro_call_diagnostics_tracked<'db>(
262    db: &'db dyn Database,
263    macro_call_id: MacroCallId<'db>,
264) -> Diagnostics<'db, SemanticDiagnostic<'db>> {
265    macro_call_diagnostics(db, macro_call_id)
266}
267
268/// Implementation of [MacroCallSemantic::macro_call_module_id].
269fn macro_call_module_id<'db>(
270    db: &'db dyn Database,
271    macro_call_id: MacroCallId<'db>,
272) -> Maybe<ModuleId<'db>> {
273    db.priv_macro_call_data(macro_call_id)?.macro_call_module
274}
275
276/// Query implementation of [MacroCallSemantic::macro_call_module_id].
277#[salsa::tracked(cycle_fn=macro_call_module_id_cycle, cycle_initial=macro_call_module_id_initial)]
278fn macro_call_module_id_tracked<'db>(
279    db: &'db dyn Database,
280    macro_call_id: MacroCallId<'db>,
281) -> Maybe<ModuleId<'db>> {
282    macro_call_module_id(db, macro_call_id)
283}
284
285/// Cycle handling for [MacroCallSemantic::macro_call_module_id].
286fn macro_call_module_id_cycle<'db>(
287    _db: &'db dyn Database,
288    _cycle: &salsa::Cycle<'_>,
289    _last_provisional_value: &Maybe<ModuleId<'db>>,
290    value: Maybe<ModuleId<'db>>,
291    _macro_call_id: MacroCallId<'db>,
292) -> Maybe<ModuleId<'db>> {
293    value
294}
295/// Cycle handling for [MacroCallSemantic::macro_call_module_id].
296fn macro_call_module_id_initial<'db>(
297    _db: &'db dyn Database,
298    _id: salsa::Id,
299    _macro_call_id: MacroCallId<'db>,
300) -> Maybe<ModuleId<'db>> {
301    Err(skip_diagnostic())
302}
303
304/// Returns the modules that are considered a part of this module.
305///
306/// If `include_all` is true, all modules are returned, regardless if exposed, or are the main
307/// module.
308#[salsa::tracked(returns(ref))]
309pub fn module_macro_modules<'db>(
310    db: &'db dyn Database,
311    include_all: bool,
312    module_id: ModuleId<'db>,
313) -> Vec<ModuleId<'db>> {
314    let mut modules = vec![];
315    let mut stack = vec![(module_id, include_all)];
316    while let Some((module_id, expose)) = stack.pop() {
317        if expose {
318            modules.push(module_id);
319        }
320        if let Ok(macro_calls) = db.module_macro_calls_ids(module_id) {
321            for macro_call in macro_calls.iter().rev() {
322                let Ok(macro_module_id) = db.macro_call_module_id(*macro_call) else {
323                    continue;
324                };
325                let expose = expose
326                    || matches!(macro_module_id, ModuleId::MacroCall { is_expose: true, .. });
327                stack.push((macro_module_id, expose));
328            }
329        }
330    }
331    modules
332}
333
334/// Trait for macro call-related semantic queries.
335pub trait MacroCallSemantic<'db>: Database {
336    /// Returns the semantic data of a macro call.
337    fn priv_macro_call_data(
338        &'db self,
339        macro_call_id: MacroCallId<'db>,
340    ) -> Maybe<MacroCallData<'db>> {
341        priv_macro_call_data_tracked(self.as_dyn_database(), macro_call_id)
342    }
343    /// Returns the expansion result of a macro call.
344    fn macro_call_module_id(&'db self, macro_call_id: MacroCallId<'db>) -> Maybe<ModuleId<'db>> {
345        macro_call_module_id_tracked(self.as_dyn_database(), macro_call_id)
346    }
347    /// Returns the semantic diagnostics of a macro call.
348    fn macro_call_diagnostics(
349        &'db self,
350        macro_call_id: MacroCallId<'db>,
351    ) -> Diagnostics<'db, SemanticDiagnostic<'db>> {
352        macro_call_diagnostics_tracked(self.as_dyn_database(), macro_call_id)
353    }
354}
355impl<'db, T: Database + ?Sized> MacroCallSemantic<'db> for T {}