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::lsp_helpers::LspHelpers;
24use crate::resolve::{ResolutionContext, ResolvedGenericItem, Resolver, ResolverMacroData};
25
26#[derive(Debug, Clone, PartialEq, Eq, salsa::Update)]
28pub struct MacroCallData<'db> {
29 pub macro_call_module: Maybe<ModuleId<'db>>,
31 pub diagnostics: Diagnostics<'db, SemanticDiagnostic<'db>>,
32 pub defsite_module_id: ModuleId<'db>,
33 pub callsite_module_id: ModuleId<'db>,
34 pub expansion_mappings: Arc<[CodeMapping]>,
35 pub parent_macro_call_data: Option<Arc<ResolverMacroData<'db>>>,
36}
37
38fn priv_macro_call_data<'db>(
40 db: &'db dyn Database,
41 macro_call_id: MacroCallId<'db>,
42) -> Maybe<MacroCallData<'db>> {
43 let inference_id = InferenceId::MacroCall(macro_call_id);
44 let module_id = macro_call_id.module_id(db);
45 let mut resolver = Resolver::new(db, module_id, inference_id);
46 let macro_call_syntax = db.module_macro_call_by_id(macro_call_id)?;
47 let macro_call_path = macro_call_syntax.path(db);
50 let macro_name = macro_call_path.as_syntax_node().get_text_without_trivia(db);
51 let callsite_module_id = macro_call_id.parent_module(db);
52 if macro_name.long(db) == EXPOSE_MACRO_NAME
54 && let Ok(None) = db.module_item_by_name(callsite_module_id, macro_name)
55 {
56 let (content, mapping) = expose_content_and_mapping(db, macro_call_syntax.arguments(db))?;
57 let code_mappings: Arc<[CodeMapping]> = [mapping].into();
58 let parent_macro_call_data = resolver.macro_call_data;
59 let generated_file_long_id = FileLongId::Virtual(VirtualFile {
60 parent: Some(macro_call_syntax.stable_ptr(db).untyped().file_id(db)),
61 name: macro_name,
62 content: SmolStrId::from(db, content),
63 code_mappings: code_mappings.clone(),
64 kind: FileKind::Module,
65 original_item_removed: false,
66 });
67 db.accumulate_inline_macro_expansion(&generated_file_long_id);
68 let generated_file_id = generated_file_long_id.intern(db);
69 let macro_call_module =
70 ModuleId::MacroCall { id: macro_call_id, generated_file_id, is_expose: true };
71 return Ok(MacroCallData {
72 macro_call_module: Ok(macro_call_module),
73 diagnostics: Default::default(),
74 defsite_module_id: callsite_module_id,
76 callsite_module_id,
77 expansion_mappings: code_mappings,
78 parent_macro_call_data,
79 });
80 }
81 let mut diagnostics = SemanticDiagnostics::default();
82 let macro_declaration_id = match resolver.resolve_generic_path(
83 &mut diagnostics,
84 ¯o_call_path,
85 NotFoundItemType::Macro,
86 ResolutionContext::Default,
87 ) {
88 Ok(ResolvedGenericItem::Macro(macro_declaration_id)) => macro_declaration_id,
89 Ok(_) => {
90 let diag_added = diagnostics.report(
91 macro_call_syntax.stable_ptr(db),
92 SemanticDiagnosticKind::InlineMacroNotFound(macro_name),
93 );
94 return Ok(MacroCallData {
95 macro_call_module: Err(diag_added),
96 diagnostics: diagnostics.build(),
97 defsite_module_id: callsite_module_id,
98 callsite_module_id,
99 expansion_mappings: Arc::new([]),
100 parent_macro_call_data: None,
101 });
102 }
103 Err(diag_added) => {
104 return Ok(MacroCallData {
105 macro_call_module: Err(diag_added),
106 diagnostics: diagnostics.build(),
107 defsite_module_id: callsite_module_id,
108 callsite_module_id,
109 expansion_mappings: Arc::new([]),
110 parent_macro_call_data: None,
111 });
112 }
113 };
114 let defsite_module_id = macro_declaration_id.parent_module(db);
115 let macro_rules = match db.macro_declaration_rules(macro_declaration_id) {
116 Ok(rules) => rules,
117 Err(diag_added) => {
118 return Ok(MacroCallData {
119 macro_call_module: Err(diag_added),
120 diagnostics: diagnostics.build(),
121 defsite_module_id,
122 callsite_module_id,
123 expansion_mappings: Arc::new([]),
124 parent_macro_call_data: None,
125 });
126 }
127 };
128 let Some((rule, (captures, placeholder_to_rep_id))) = macro_rules.iter().find_map(|rule| {
129 is_macro_rule_match(db, rule, ¯o_call_syntax.arguments(db)).map(|res| (rule, res))
130 }) else {
131 let diag_added = diagnostics.report(
132 macro_call_syntax.stable_ptr(db),
133 SemanticDiagnosticKind::InlineMacroNoMatchingRule(macro_name),
134 );
135 return Ok(MacroCallData {
136 macro_call_module: Err(diag_added),
137 diagnostics: diagnostics.build(),
138 defsite_module_id,
139 callsite_module_id,
140 expansion_mappings: Arc::new([]),
141 parent_macro_call_data: None,
142 });
143 };
144 let mut matcher_ctx = MatcherContext { captures, placeholder_to_rep_id, ..Default::default() };
145 let expanded_code = expand_macro_rule(db, rule, &mut matcher_ctx).unwrap();
146 let parent_macro_call_data = resolver.macro_call_data;
147 let generated_file_long_id = FileLongId::Virtual(VirtualFile {
148 parent: Some(macro_call_syntax.stable_ptr(db).untyped().file_id(db)),
149 name: macro_name,
150 content: SmolStrId::from_arcstr(db, &expanded_code.text),
151 code_mappings: expanded_code.code_mappings.clone(),
152 kind: FileKind::Module,
153 original_item_removed: false,
154 });
155 db.accumulate_inline_macro_expansion(&generated_file_long_id);
156 let generated_file_id = generated_file_long_id.intern(db);
157 let macro_call_module =
158 ModuleId::MacroCall { id: macro_call_id, generated_file_id, is_expose: false };
159 Ok(MacroCallData {
160 macro_call_module: Ok(macro_call_module),
161 diagnostics: diagnostics.build(),
162 defsite_module_id,
163 callsite_module_id,
164 expansion_mappings: expanded_code.code_mappings,
165 parent_macro_call_data,
166 })
167}
168
169#[salsa::tracked(cycle_fn=priv_macro_call_data_cycle, cycle_initial=priv_macro_call_data_initial)]
171fn priv_macro_call_data_tracked<'db>(
172 db: &'db dyn Database,
173 macro_call_id: MacroCallId<'db>,
174) -> Maybe<MacroCallData<'db>> {
175 priv_macro_call_data(db, macro_call_id)
176}
177
178pub const EXPOSE_MACRO_NAME: &str = "expose";
180
181pub fn expose_content_and_mapping<'db>(
184 db: &'db dyn Database,
185 args: ast::TokenTreeNode<'db>,
186) -> Maybe<(String, CodeMapping)> {
187 let tokens = match args.subtree(db) {
188 ast::WrappedTokenTree::Parenthesized(tree) => tree.tokens(db),
189 ast::WrappedTokenTree::Braced(tree) => tree.tokens(db),
190 ast::WrappedTokenTree::Bracketed(tree) => tree.tokens(db),
191 ast::WrappedTokenTree::Missing(_) => return Err(skip_diagnostic()),
192 };
193 let tokens_node = tokens.as_syntax_node();
194 let tokens_span = tokens_node.span(db);
195 Ok((
196 tokens_node.get_text(db).to_string(),
197 CodeMapping {
198 span: TextSpan::new_with_width(TextOffset::START, tokens_span.width()),
199 origin: CodeOrigin::Start(tokens_span.start),
200 },
201 ))
202}
203
204fn priv_macro_call_data_cycle<'db>(
206 _db: &'db dyn Database,
207 _value: &Maybe<MacroCallData<'db>>,
208 _count: u32,
209 _macro_call_id: MacroCallId<'db>,
210) -> salsa::CycleRecoveryAction<Maybe<MacroCallData<'db>>> {
211 salsa::CycleRecoveryAction::Iterate
212}
213fn priv_macro_call_data_initial<'db>(
215 db: &'db dyn Database,
216 macro_call_id: MacroCallId<'db>,
217) -> Maybe<MacroCallData<'db>> {
218 let mut diagnostics = SemanticDiagnostics::default();
221 let macro_call_syntax = db.module_macro_call_by_id(macro_call_id)?;
222 let macro_call_path = macro_call_syntax.path(db);
223 let macro_name = macro_call_path.as_syntax_node().get_text_without_trivia(db);
224
225 let diag_added = diagnostics.report(
226 macro_call_id.stable_ptr(db).untyped(),
227 SemanticDiagnosticKind::InlineMacroNotFound(macro_name),
228 );
229 let module_id = macro_call_id.parent_module(db);
230
231 Ok(MacroCallData {
232 macro_call_module: Err(diag_added),
233 diagnostics: diagnostics.build(),
234 defsite_module_id: module_id,
235 callsite_module_id: module_id,
236 expansion_mappings: Arc::new([]),
237 parent_macro_call_data: None,
238 })
239}
240
241fn macro_call_diagnostics<'db>(
243 db: &'db dyn Database,
244 macro_call_id: MacroCallId<'db>,
245) -> Diagnostics<'db, SemanticDiagnostic<'db>> {
246 db.priv_macro_call_data(macro_call_id).map(|data| data.diagnostics).unwrap_or_default()
247}
248
249#[salsa::tracked]
251fn macro_call_diagnostics_tracked<'db>(
252 db: &'db dyn Database,
253 macro_call_id: MacroCallId<'db>,
254) -> Diagnostics<'db, SemanticDiagnostic<'db>> {
255 macro_call_diagnostics(db, macro_call_id)
256}
257
258fn macro_call_module_id<'db>(
260 db: &'db dyn Database,
261 macro_call_id: MacroCallId<'db>,
262) -> Maybe<ModuleId<'db>> {
263 db.priv_macro_call_data(macro_call_id)?.macro_call_module
264}
265
266#[salsa::tracked(cycle_fn=macro_call_module_id_cycle, cycle_initial=macro_call_module_id_initial)]
268fn macro_call_module_id_tracked<'db>(
269 db: &'db dyn Database,
270 macro_call_id: MacroCallId<'db>,
271) -> Maybe<ModuleId<'db>> {
272 macro_call_module_id(db, macro_call_id)
273}
274
275fn macro_call_module_id_cycle<'db>(
277 _db: &'db dyn Database,
278 _value: &Maybe<ModuleId<'db>>,
279 _count: u32,
280 _macro_call_id: MacroCallId<'db>,
281) -> salsa::CycleRecoveryAction<Maybe<ModuleId<'db>>> {
282 salsa::CycleRecoveryAction::Iterate
283}
284fn macro_call_module_id_initial<'db>(
286 _db: &'db dyn Database,
287 _macro_call_id: MacroCallId<'db>,
288) -> Maybe<ModuleId<'db>> {
289 Err(skip_diagnostic())
290}
291
292#[salsa::tracked(returns(ref))]
297pub fn module_macro_modules<'db>(
298 db: &'db dyn Database,
299 include_all: bool,
300 module_id: ModuleId<'db>,
301) -> Vec<ModuleId<'db>> {
302 let mut modules = vec![];
303 let mut stack = vec![(module_id, include_all)];
304 while let Some((module_id, expose)) = stack.pop() {
305 if expose {
306 modules.push(module_id);
307 }
308 if let Ok(macro_calls) = db.module_macro_calls_ids(module_id) {
309 for macro_call in macro_calls.iter().rev() {
310 let Ok(macro_module_id) = db.macro_call_module_id(*macro_call) else {
311 continue;
312 };
313 let expose = expose
314 || matches!(macro_module_id, ModuleId::MacroCall { is_expose: true, .. });
315 stack.push((macro_module_id, expose));
316 }
317 }
318 }
319 modules
320}
321
322pub trait MacroCallSemantic<'db>: Database {
324 fn priv_macro_call_data(
326 &'db self,
327 macro_call_id: MacroCallId<'db>,
328 ) -> Maybe<MacroCallData<'db>> {
329 priv_macro_call_data_tracked(self.as_dyn_database(), macro_call_id)
330 }
331 fn macro_call_module_id(&'db self, macro_call_id: MacroCallId<'db>) -> Maybe<ModuleId<'db>> {
333 macro_call_module_id_tracked(self.as_dyn_database(), macro_call_id)
334 }
335 fn macro_call_diagnostics(
337 &'db self,
338 macro_call_id: MacroCallId<'db>,
339 ) -> Diagnostics<'db, SemanticDiagnostic<'db>> {
340 macro_call_diagnostics_tracked(self.as_dyn_database(), macro_call_id)
341 }
342}
343impl<'db, T: Database + ?Sized> MacroCallSemantic<'db> for T {}