1use std::sync::Arc;
2
3use cairo_lang_defs::db::DefsGroup;
4use cairo_lang_defs::ids::{
5 GlobalUseId, LanguageElementId, LookupItemId, ModuleId, ModuleItemId, UseId,
6};
7use cairo_lang_diagnostics::{Diagnostics, Maybe};
8use cairo_lang_filesystem::ids::Tracked;
9use cairo_lang_proc_macros::DebugWithDb;
10use cairo_lang_syntax::node::helpers::UsePathEx;
11use cairo_lang_syntax::node::kind::SyntaxKind;
12use cairo_lang_syntax::node::{TypedSyntaxNode, ast};
13use cairo_lang_utils::ordered_hash_map::{Entry, OrderedHashMap};
14use cairo_lang_utils::unordered_hash_set::UnorderedHashSet;
15use itertools::{Itertools, chain};
16use salsa::Database;
17
18use super::module::get_module_global_uses;
19use super::visibility::peek_visible_in;
20use crate::SemanticDiagnostic;
21use crate::diagnostic::SemanticDiagnosticKind::*;
22use crate::diagnostic::{
23 ElementKind, NotFoundItemType, SemanticDiagnostics, SemanticDiagnosticsBuilder,
24};
25use crate::expr::inference::InferenceId;
26use crate::items::macro_call::module_macro_modules;
27use crate::resolve::{ResolutionContext, ResolvedGenericItem, Resolver, ResolverData};
28
29#[derive(Clone, Debug, PartialEq, Eq, DebugWithDb, salsa::Update)]
30#[debug_db(dyn Database)]
31pub struct UseData<'db> {
32 diagnostics: Diagnostics<'db, SemanticDiagnostic<'db>>,
33 resolved_item: Maybe<ResolvedGenericItem<'db>>,
34 resolver_data: Arc<ResolverData<'db>>,
35}
36
37pub struct UseAsPathSegments<'db> {
39 pub segments: Vec<ast::PathSegment<'db>>,
40 pub is_placeholder: Option<ast::TerminalDollar<'db>>,
41}
42
43pub enum UsePathOrDollar<'db> {
46 UsePathSingle(ast::UsePathSingle<'db>),
47 Dollar(ast::TerminalDollar<'db>),
48 None,
49}
50
51fn priv_use_semantic_data<'db>(
53 db: &'db dyn Database,
54 use_id: UseId<'db>,
55) -> Maybe<Arc<UseData<'db>>> {
56 let module_id = use_id.module_id(db);
57 let mut diagnostics = SemanticDiagnostics::default();
58 let module_item_id = ModuleItemId::Use(use_id);
59 let inference_id = InferenceId::LookupItemDeclaration(LookupItemId::ModuleItem(module_item_id));
60 let mut resolver = Resolver::new(db, module_id, inference_id);
61 let use_ast = ast::UsePath::Leaf(db.module_use_by_id(use_id)?);
65 let item = use_ast.get_item(db);
66 resolver.set_feature_config(&use_id, &item, &mut diagnostics);
67 let resolved_item = resolver.resolve_use_path(
68 &mut diagnostics,
69 use_ast,
70 ResolutionContext::ModuleItem(module_item_id),
71 );
72 let resolver_data: Arc<ResolverData<'_>> = Arc::new(resolver.data);
73
74 Ok(Arc::new(UseData { diagnostics: diagnostics.build(), resolved_item, resolver_data }))
75}
76
77#[salsa::tracked(cycle_result=priv_use_semantic_data_cycle)]
79fn priv_use_semantic_data_tracked<'db>(
80 db: &'db dyn Database,
81 use_id: UseId<'db>,
82) -> Maybe<Arc<UseData<'db>>> {
83 priv_use_semantic_data(db, use_id)
84}
85
86pub fn get_use_path_segments<'db>(
95 db: &'db dyn Database,
96 use_path: ast::UsePath<'db>,
97) -> Maybe<UseAsPathSegments<'db>> {
98 let mut rev_segments = vec![];
99 match &use_path {
100 ast::UsePath::Leaf(use_ast) => rev_segments.push(use_ast.ident(db)),
101 ast::UsePath::Single(use_ast) => rev_segments.push(use_ast.ident(db)),
102 ast::UsePath::Star(_) => {}
103 ast::UsePath::Multi(_) => {
104 panic!("Only `UsePathLeaf` and `UsePathSingle` are supported.")
105 }
106 }
107 let mut current_path = use_path;
108 let mut dollar = None;
109 loop {
110 match get_parent_single_use_path(db, ¤t_path)? {
111 UsePathOrDollar::UsePathSingle(parent_use_path) => {
112 let ident = parent_use_path.ident(db);
113 rev_segments.push(ident);
114 current_path = ast::UsePath::Single(parent_use_path);
115 }
116 UsePathOrDollar::Dollar(d) => {
117 dollar = Some(d);
118 break;
119 }
120 UsePathOrDollar::None => break,
121 }
122 }
123 Ok(UseAsPathSegments {
124 segments: rev_segments.into_iter().rev().collect(),
125 is_placeholder: dollar,
126 })
127}
128
129fn get_parent_single_use_path<'db>(
131 db: &'db dyn Database,
132 use_path: &ast::UsePath<'db>,
133) -> Maybe<UsePathOrDollar<'db>> {
134 let node = use_path.as_syntax_node();
135 let parent = node.parent(db).expect("`UsePath` is not under an `ItemUse`.");
136 match parent.kind(db) {
137 SyntaxKind::UsePathSingle => {
138 Ok(UsePathOrDollar::UsePathSingle(ast::UsePathSingle::from_syntax_node(db, parent)))
139 }
140 SyntaxKind::ItemUse => {
141 let typed_use_node = ast::ItemUse::from_syntax_node(db, parent);
142 Ok(match typed_use_node.dollar(db) {
143 ast::OptionTerminalDollar::TerminalDollar(dollar) => {
144 UsePathOrDollar::Dollar(dollar)
145 }
146 ast::OptionTerminalDollar::Empty(_) => UsePathOrDollar::None,
147 })
148 }
149 SyntaxKind::UsePathList | SyntaxKind::UsePathMulti => {
150 let mut current = parent;
151 while let Some(parent) = current.parent(db) {
152 match parent.kind(db) {
153 SyntaxKind::UsePathSingle => {
154 return Ok(UsePathOrDollar::UsePathSingle(
155 ast::UsePathSingle::from_syntax_node(db, parent),
156 ));
157 }
158 SyntaxKind::ItemUse => {
159 let item_use = ast::ItemUse::from_syntax_node(db, parent);
160 if let ast::OptionTerminalDollar::TerminalDollar(d) = item_use.dollar(db) {
161 return Ok(UsePathOrDollar::Dollar(d));
162 }
163 return Ok(UsePathOrDollar::None);
164 }
165 SyntaxKind::UsePathList | SyntaxKind::UsePathMulti => {
166 current = parent;
167 continue;
168 }
169 _ => return Ok(UsePathOrDollar::None),
170 }
171 }
172 Ok(UsePathOrDollar::None)
173 }
174 _ => Ok(UsePathOrDollar::None),
175 }
176}
177
178fn priv_use_semantic_data_cycle<'db>(
180 db: &'db dyn Database,
181 use_id: UseId<'db>,
182) -> Maybe<Arc<UseData<'db>>> {
183 let module_id = use_id.module_id(db);
184 let mut diagnostics = SemanticDiagnostics::default();
185 let use_ast = db.module_use_by_id(use_id)?;
186 let err = Err(diagnostics.report(use_ast.stable_ptr(db), UseCycle));
187 let inference_id =
188 InferenceId::LookupItemDeclaration(LookupItemId::ModuleItem(ModuleItemId::Use(use_id)));
189 Ok(Arc::new(UseData {
190 diagnostics: diagnostics.build(),
191 resolved_item: err,
192 resolver_data: Arc::new(ResolverData::new(module_id, inference_id)),
193 }))
194}
195
196fn use_semantic_diagnostics<'db>(
198 db: &'db dyn Database,
199 use_id: UseId<'db>,
200) -> Diagnostics<'db, SemanticDiagnostic<'db>> {
201 db.priv_use_semantic_data(use_id).map(|data| data.diagnostics.clone()).unwrap_or_default()
202}
203
204#[salsa::tracked]
206fn use_semantic_diagnostics_tracked<'db>(
207 db: &'db dyn Database,
208 use_id: UseId<'db>,
209) -> Diagnostics<'db, SemanticDiagnostic<'db>> {
210 use_semantic_diagnostics(db, use_id)
211}
212
213fn use_resolver_data<'db>(
215 db: &'db dyn Database,
216 use_id: UseId<'db>,
217) -> Maybe<Arc<ResolverData<'db>>> {
218 Ok(db.priv_use_semantic_data(use_id)?.resolver_data.clone())
219}
220
221#[salsa::tracked(cycle_result=use_resolver_data_cycle)]
223fn use_resolver_data_tracked<'db>(
224 db: &'db dyn Database,
225 use_id: UseId<'db>,
226) -> Maybe<Arc<ResolverData<'db>>> {
227 use_resolver_data(db, use_id)
228}
229
230fn use_resolver_data_cycle<'db>(
232 db: &'db dyn Database,
233 use_id: UseId<'db>,
234) -> Maybe<Arc<ResolverData<'db>>> {
235 use_resolver_data(db, use_id)
237}
238
239pub trait SemanticUseEx<'a>: Database {
240 fn use_resolved_item(&'a self, use_id: UseId<'a>) -> Maybe<ResolvedGenericItem<'a>> {
244 let db = self.as_dyn_database();
245 db.priv_use_semantic_data(use_id)?.resolved_item.clone()
246 }
247}
248
249impl<'a, T: Database + ?Sized> SemanticUseEx<'a> for T {}
250
251#[derive(Clone, Debug, PartialEq, Eq, DebugWithDb, salsa::Update)]
252#[debug_db(dyn Database)]
253pub struct UseGlobalData<'db> {
254 diagnostics: Diagnostics<'db, SemanticDiagnostic<'db>>,
255 imported_module: Maybe<ModuleId<'db>>,
256}
257
258fn priv_global_use_semantic_data<'db>(
260 db: &'db dyn Database,
261 global_use_id: GlobalUseId<'db>,
262) -> Maybe<UseGlobalData<'db>> {
263 let module_id = global_use_id.module_id(db);
264 let mut diagnostics = SemanticDiagnostics::default();
265 let inference_id = InferenceId::GlobalUseStar(global_use_id);
266 let star_ast = ast::UsePath::Star(db.module_global_use_by_id(global_use_id)?);
267 let mut resolver = Resolver::new(db, module_id, inference_id);
268 let edition = resolver.settings.edition;
269 if edition.ignore_visibility() {
270 diagnostics.report(star_ast.stable_ptr(db), GlobalUsesNotSupportedInEdition(edition));
272 }
273
274 let item = star_ast.get_item(db);
275 let segments = get_use_path_segments(db, star_ast.clone())?;
276 let Some(last_segment) = segments.segments.last() else {
277 let imported_module = Err(diagnostics.report(star_ast.stable_ptr(db), UseStarEmptyPath));
278 return Ok(UseGlobalData { diagnostics: diagnostics.build(), imported_module });
279 };
280 let last_element_ptr = last_segment.stable_ptr(db);
281 resolver.set_feature_config(&global_use_id, &item, &mut diagnostics);
282 let imported_module = resolver
283 .resolve_generic_path(
284 &mut diagnostics,
285 segments,
286 NotFoundItemType::Identifier,
287 ResolutionContext::Default,
288 )
289 .and_then(|resolved_item| match &resolved_item {
290 ResolvedGenericItem::Module(module_id) => Ok(*module_id),
291 other => Err(diagnostics.report(
292 last_element_ptr,
293 UnexpectedElement { expected: vec![ElementKind::Module], actual: other.into() },
294 )),
295 });
296 if imported_module == Ok(module_id) {
297 diagnostics.report(star_ast.stable_ptr(db), SelfGlobalUse);
298 }
299 Ok(UseGlobalData { diagnostics: diagnostics.build(), imported_module })
300}
301
302#[salsa::tracked(cycle_fn=priv_global_use_semantic_data_tracked_cycle_fn, cycle_initial=priv_global_use_semantic_data_tracked_initial)]
304fn priv_global_use_semantic_data_tracked<'db>(
305 db: &'db dyn Database,
306 global_use_id: GlobalUseId<'db>,
307) -> Maybe<UseGlobalData<'db>> {
308 priv_global_use_semantic_data(db, global_use_id)
309}
310
311fn priv_global_use_semantic_data_tracked_cycle_fn<'db>(
313 _db: &'db dyn Database,
314 _value: &Maybe<UseGlobalData<'db>>,
315 _count: u32,
316 _global_use_id: GlobalUseId<'db>,
317) -> salsa::CycleRecoveryAction<Maybe<UseGlobalData<'db>>> {
318 salsa::CycleRecoveryAction::Iterate
319}
320
321fn priv_global_use_semantic_data_tracked_initial<'db>(
323 db: &'db dyn Database,
324 global_use_id: GlobalUseId<'db>,
325) -> Maybe<UseGlobalData<'db>> {
326 let mut diagnostics = SemanticDiagnostics::default();
327 let global_use_ast = db.module_global_use_by_id(global_use_id)?;
328 let star_ast = ast::UsePath::Star(db.module_global_use_by_id(global_use_id)?);
329 let segments = get_use_path_segments(db, star_ast)?;
330 let err = if segments.segments.len() == 1 {
331 diagnostics.report(
335 segments.segments.last().unwrap().stable_ptr(db),
336 PathNotFound(NotFoundItemType::Identifier),
337 )
338 } else {
339 diagnostics.report(global_use_ast.stable_ptr(db), UseCycle)
340 };
341 Ok(UseGlobalData { diagnostics: diagnostics.build(), imported_module: Err(err) })
342}
343
344fn priv_global_use_imported_module<'db>(
346 db: &'db dyn Database,
347 global_use_id: GlobalUseId<'db>,
348) -> Maybe<ModuleId<'db>> {
349 db.priv_global_use_semantic_data(global_use_id)?.imported_module
350}
351
352#[salsa::tracked]
354fn priv_global_use_imported_module_tracked<'db>(
355 db: &'db dyn Database,
356 global_use_id: GlobalUseId<'db>,
357) -> Maybe<ModuleId<'db>> {
358 priv_global_use_imported_module(db, global_use_id)
359}
360
361fn global_use_semantic_diagnostics<'db>(
363 db: &'db dyn Database,
364 global_use_id: GlobalUseId<'db>,
365) -> Diagnostics<'db, SemanticDiagnostic<'db>> {
366 db.priv_global_use_semantic_data(global_use_id).map(|data| data.diagnostics).unwrap_or_default()
367}
368
369#[salsa::tracked]
371fn global_use_semantic_diagnostics_tracked<'db>(
372 db: &'db dyn Database,
373 global_use_id: GlobalUseId<'db>,
374) -> Diagnostics<'db, SemanticDiagnostic<'db>> {
375 global_use_semantic_diagnostics(db, global_use_id)
376}
377
378pub type ImportedModules<'db> = OrderedHashMap<ModuleId<'db>, ImportInfo<'db>>;
380
381#[derive(Debug, Default, Clone, PartialEq, Eq, salsa::Update)]
383pub struct ImportInfo<'db> {
384 pub user_modules: Vec<ModuleId<'db>>,
386}
387
388#[salsa::tracked(returns(ref),cycle_fn=module_imported_modules_cycle_fn, cycle_initial=module_imported_modules_initial)]
391fn module_imported_modules<'db>(
392 db: &'db dyn Database,
393 _tracked: Tracked,
394 module_id: ModuleId<'db>,
395) -> ImportedModules<'db> {
396 let mut visited = UnorderedHashSet::<_>::default();
397 let mut stack = vec![(module_id, module_id)];
398 let mut modules = OrderedHashMap::<ModuleId<'db>, ImportInfo<'db>>::default();
399 modules.insert(module_id, ImportInfo { user_modules: vec![module_id] });
400 while let Some((user_module, containing_module)) = stack.pop() {
403 if !visited.insert((user_module, containing_module)) {
404 continue;
405 }
406 for defined_module in
407 chain!([&containing_module], module_macro_modules(db, false, containing_module))
408 {
409 let Ok(glob_uses) = get_module_global_uses(db, *defined_module) else {
410 continue;
411 };
412 for (glob_use, item_visibility) in glob_uses.iter() {
413 let Ok(module_id_found) = db.priv_global_use_imported_module(*glob_use) else {
414 continue;
415 };
416 let entry = modules.entry(module_id_found).or_default();
418 if peek_visible_in(db, *item_visibility, containing_module, user_module) {
419 stack.push((containing_module, module_id_found));
420 entry.user_modules.push(containing_module);
421 }
422 }
423 }
424 }
425 let mut stack =
426 modules.iter().filter(|(_, v)| v.user_modules.is_empty()).map(|(k, _)| *k).collect_vec();
427 while let Some(curr_module_id) = stack.pop() {
429 for defined_module in
430 chain!([&curr_module_id], module_macro_modules(db, false, curr_module_id))
431 {
432 let Ok(glob_uses) = get_module_global_uses(db, *defined_module) else { continue };
433 for glob_use in glob_uses.keys() {
434 if let Ok(module_id_found) = db.priv_global_use_imported_module(*glob_use)
435 && let Entry::Vacant(entry) = modules.entry(module_id_found)
436 {
437 entry.insert(ImportInfo::default());
438 stack.push(module_id_found);
439 }
440 }
441 }
442 }
443 modules
444}
445
446fn module_imported_modules_cycle_fn<'db>(
448 _db: &dyn Database,
449 _value: &ImportedModules<'db>,
450 _count: u32,
451 _tracked: Tracked,
452 _module_id: ModuleId<'db>,
453) -> salsa::CycleRecoveryAction<ImportedModules<'db>> {
454 salsa::CycleRecoveryAction::Iterate
455}
456
457fn module_imported_modules_initial<'db>(
459 _db: &'db dyn Database,
460 _tracked: Tracked,
461 _module_id: ModuleId<'db>,
462) -> ImportedModules<'db> {
463 Default::default()
464}
465
466pub trait UseSemantic<'db>: Database {
468 fn priv_use_semantic_data(&'db self, use_id: UseId<'db>) -> Maybe<Arc<UseData<'db>>> {
470 priv_use_semantic_data_tracked(self.as_dyn_database(), use_id)
471 }
472 fn use_semantic_diagnostics(
474 &'db self,
475 use_id: UseId<'db>,
476 ) -> Diagnostics<'db, SemanticDiagnostic<'db>> {
477 use_semantic_diagnostics_tracked(self.as_dyn_database(), use_id)
478 }
479 fn use_resolver_data(&'db self, use_id: UseId<'db>) -> Maybe<Arc<ResolverData<'db>>> {
481 use_resolver_data_tracked(self.as_dyn_database(), use_id)
482 }
483 fn priv_global_use_semantic_data(
485 &'db self,
486 global_use_id: GlobalUseId<'db>,
487 ) -> Maybe<UseGlobalData<'db>> {
488 priv_global_use_semantic_data_tracked(self.as_dyn_database(), global_use_id)
489 }
490 fn priv_global_use_imported_module(
492 &'db self,
493 global_use_id: GlobalUseId<'db>,
494 ) -> Maybe<ModuleId<'db>> {
495 priv_global_use_imported_module_tracked(self.as_dyn_database(), global_use_id)
496 }
497 fn global_use_semantic_diagnostics(
499 &'db self,
500 global_use_id: GlobalUseId<'db>,
501 ) -> Diagnostics<'db, SemanticDiagnostic<'db>> {
502 global_use_semantic_diagnostics_tracked(self.as_dyn_database(), global_use_id)
503 }
504 fn module_imported_modules(
506 &'db self,
507 _tracked: Tracked,
508 module_id: ModuleId<'db>,
509 ) -> &'db ImportedModules<'db> {
510 module_imported_modules(self.as_dyn_database(), _tracked, module_id)
511 }
512}
513impl<'db, T: Database + ?Sized> UseSemantic<'db> for T {}