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#[derive(Debug, Clone, PartialEq, Eq, salsa::Update)]
27pub struct MacroCallData<'db> {
28 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
37fn 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 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 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_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 ¯o_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, ¯o_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 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#[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
185pub const EXPOSE_MACRO_NAME: &str = "expose";
187
188pub 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
211fn 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
222fn 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 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
251fn 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#[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
268fn 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#[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
285fn 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}
295fn 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#[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
334pub trait MacroCallSemantic<'db>: Database {
336 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 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 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 {}