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 parent_macro_call_data = resolver.macro_call_data;
58 let generated_file_id = FileLongId::Virtual(VirtualFile {
59 parent: Some(macro_call_syntax.stable_ptr(db).untyped().span_in_file(db)),
60 name: macro_name,
61 content: SmolStrId::from(db, content),
62 code_mappings: code_mappings.clone(),
63 kind: FileKind::Module,
64 original_item_removed: false,
65 })
66 .intern(db);
67 let macro_call_module =
68 ModuleId::MacroCall { id: macro_call_id, generated_file_id, is_expose: true };
69 return Ok(MacroCallData {
70 macro_call_module: Ok(macro_call_module),
71 diagnostics: Default::default(),
72 defsite_module_id: callsite_module_id,
74 callsite_module_id,
75 expansion_mappings: code_mappings,
76 parent_macro_call_data,
77 });
78 }
79 let mut diagnostics = SemanticDiagnostics::default();
80 let macro_declaration_id = match resolver.resolve_generic_path(
81 &mut diagnostics,
82 ¯o_call_path,
83 NotFoundItemType::Macro,
84 ResolutionContext::Default,
85 ) {
86 Ok(ResolvedGenericItem::Macro(macro_declaration_id)) => macro_declaration_id,
87 Ok(_) => {
88 let diag_added = diagnostics.report(
89 macro_call_syntax.stable_ptr(db),
90 SemanticDiagnosticKind::InlineMacroNotFound(macro_name),
91 );
92 return Ok(MacroCallData {
93 macro_call_module: Err(diag_added),
94 diagnostics: diagnostics.build(),
95 defsite_module_id: callsite_module_id,
96 callsite_module_id,
97 expansion_mappings: Arc::new([]),
98 parent_macro_call_data: None,
99 });
100 }
101 Err(diag_added) => {
102 return Ok(MacroCallData {
103 macro_call_module: Err(diag_added),
104 diagnostics: diagnostics.build(),
105 defsite_module_id: callsite_module_id,
106 callsite_module_id,
107 expansion_mappings: Arc::new([]),
108 parent_macro_call_data: None,
109 });
110 }
111 };
112 let defsite_module_id = macro_declaration_id.parent_module(db);
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: None,
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: None,
140 });
141 };
142 let mut matcher_ctx = MatcherContext { captures, placeholder_to_rep_id, ..Default::default() };
143 let expanded_code = expand_macro_rule(db, rule, &mut matcher_ctx).unwrap();
144 let parent_macro_call_data = resolver.macro_call_data;
145 let generated_file_id = FileLongId::Virtual(VirtualFile {
146 parent: Some(macro_call_syntax.stable_ptr(db).untyped().span_in_file(db)),
147 name: macro_name,
148 content: SmolStrId::from_arcstr(db, &expanded_code.text),
149 code_mappings: expanded_code.code_mappings.clone(),
150 kind: FileKind::Module,
151 original_item_removed: false,
152 })
153 .intern(db);
154 let macro_call_module =
155 ModuleId::MacroCall { id: macro_call_id, generated_file_id, is_expose: false };
156 Ok(MacroCallData {
157 macro_call_module: Ok(macro_call_module),
158 diagnostics: diagnostics.build(),
159 defsite_module_id,
160 callsite_module_id,
161 expansion_mappings: expanded_code.code_mappings,
162 parent_macro_call_data,
163 })
164}
165
166#[salsa::tracked(cycle_fn=priv_macro_call_data_cycle, cycle_initial=priv_macro_call_data_initial)]
168fn priv_macro_call_data_tracked<'db>(
169 db: &'db dyn Database,
170 macro_call_id: MacroCallId<'db>,
171) -> Maybe<MacroCallData<'db>> {
172 priv_macro_call_data(db, macro_call_id)
173}
174
175pub const EXPOSE_MACRO_NAME: &str = "expose";
177
178pub fn expose_content_and_mapping<'db>(
181 db: &'db dyn Database,
182 args: ast::TokenTreeNode<'db>,
183) -> Maybe<(String, CodeMapping)> {
184 let tokens = match args.subtree(db) {
185 ast::WrappedTokenTree::Parenthesized(tree) => tree.tokens(db),
186 ast::WrappedTokenTree::Braced(tree) => tree.tokens(db),
187 ast::WrappedTokenTree::Bracketed(tree) => tree.tokens(db),
188 ast::WrappedTokenTree::Missing(_) => return Err(skip_diagnostic()),
189 };
190 let tokens_node = tokens.as_syntax_node();
191 let tokens_span = tokens_node.span(db);
192 Ok((
193 tokens_node.get_text(db).to_string(),
194 CodeMapping {
195 span: TextSpan::new_with_width(TextOffset::START, tokens_span.width()),
196 origin: CodeOrigin::Start(tokens_span.start),
197 },
198 ))
199}
200
201fn priv_macro_call_data_cycle<'db>(
203 _db: &'db dyn Database,
204 _value: &Maybe<MacroCallData<'db>>,
205 _count: u32,
206 _macro_call_id: MacroCallId<'db>,
207) -> salsa::CycleRecoveryAction<Maybe<MacroCallData<'db>>> {
208 salsa::CycleRecoveryAction::Iterate
209}
210fn priv_macro_call_data_initial<'db>(
212 db: &'db dyn Database,
213 macro_call_id: MacroCallId<'db>,
214) -> Maybe<MacroCallData<'db>> {
215 let mut diagnostics = SemanticDiagnostics::default();
218 let macro_call_syntax = db.module_macro_call_by_id(macro_call_id)?;
219 let macro_call_path = macro_call_syntax.path(db);
220 let macro_name = macro_call_path.as_syntax_node().get_text_without_trivia(db);
221
222 let diag_added = diagnostics.report(
223 macro_call_id.stable_ptr(db).untyped(),
224 SemanticDiagnosticKind::InlineMacroNotFound(macro_name),
225 );
226 let module_id = macro_call_id.parent_module(db);
227
228 Ok(MacroCallData {
229 macro_call_module: Err(diag_added),
230 diagnostics: diagnostics.build(),
231 defsite_module_id: module_id,
232 callsite_module_id: module_id,
233 expansion_mappings: Arc::new([]),
234 parent_macro_call_data: None,
235 })
236}
237
238fn macro_call_diagnostics<'db>(
240 db: &'db dyn Database,
241 macro_call_id: MacroCallId<'db>,
242) -> Diagnostics<'db, SemanticDiagnostic<'db>> {
243 db.priv_macro_call_data(macro_call_id).map(|data| data.diagnostics).unwrap_or_default()
244}
245
246#[salsa::tracked]
248fn macro_call_diagnostics_tracked<'db>(
249 db: &'db dyn Database,
250 macro_call_id: MacroCallId<'db>,
251) -> Diagnostics<'db, SemanticDiagnostic<'db>> {
252 macro_call_diagnostics(db, macro_call_id)
253}
254
255fn macro_call_module_id<'db>(
257 db: &'db dyn Database,
258 macro_call_id: MacroCallId<'db>,
259) -> Maybe<ModuleId<'db>> {
260 db.priv_macro_call_data(macro_call_id)?.macro_call_module
261}
262
263#[salsa::tracked(cycle_fn=macro_call_module_id_cycle, cycle_initial=macro_call_module_id_initial)]
265fn macro_call_module_id_tracked<'db>(
266 db: &'db dyn Database,
267 macro_call_id: MacroCallId<'db>,
268) -> Maybe<ModuleId<'db>> {
269 macro_call_module_id(db, macro_call_id)
270}
271
272fn macro_call_module_id_cycle<'db>(
274 _db: &'db dyn Database,
275 _value: &Maybe<ModuleId<'db>>,
276 _count: u32,
277 _macro_call_id: MacroCallId<'db>,
278) -> salsa::CycleRecoveryAction<Maybe<ModuleId<'db>>> {
279 salsa::CycleRecoveryAction::Iterate
280}
281fn macro_call_module_id_initial<'db>(
283 _db: &'db dyn Database,
284 _macro_call_id: MacroCallId<'db>,
285) -> Maybe<ModuleId<'db>> {
286 Err(skip_diagnostic())
287}
288
289#[salsa::tracked(returns(ref))]
294pub fn module_macro_modules<'db>(
295 db: &'db dyn Database,
296 include_all: bool,
297 module_id: ModuleId<'db>,
298) -> Vec<ModuleId<'db>> {
299 let mut modules = vec![];
300 let mut stack = vec![(module_id, include_all)];
301 while let Some((module_id, expose)) = stack.pop() {
302 if expose {
303 modules.push(module_id);
304 }
305 if let Ok(macro_calls) = db.module_macro_calls_ids(module_id) {
306 for macro_call in macro_calls.iter().rev() {
307 let Ok(macro_module_id) = db.macro_call_module_id(*macro_call) else {
308 continue;
309 };
310 let expose = expose
311 || matches!(macro_module_id, ModuleId::MacroCall { is_expose: true, .. });
312 stack.push((macro_module_id, expose));
313 }
314 }
315 }
316 modules
317}
318
319pub trait MacroCallSemantic<'db>: Database {
321 fn priv_macro_call_data(
323 &'db self,
324 macro_call_id: MacroCallId<'db>,
325 ) -> Maybe<MacroCallData<'db>> {
326 priv_macro_call_data_tracked(self.as_dyn_database(), macro_call_id)
327 }
328 fn macro_call_module_id(&'db self, macro_call_id: MacroCallId<'db>) -> Maybe<ModuleId<'db>> {
330 macro_call_module_id_tracked(self.as_dyn_database(), macro_call_id)
331 }
332 fn macro_call_diagnostics(
334 &'db self,
335 macro_call_id: MacroCallId<'db>,
336 ) -> Diagnostics<'db, SemanticDiagnostic<'db>> {
337 macro_call_diagnostics_tracked(self.as_dyn_database(), macro_call_id)
338 }
339}
340impl<'db, T: Database + ?Sized> MacroCallSemantic<'db> for T {}