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.parent_module(db);
57 let mut diagnostics = SemanticDiagnostics::new(module_id);
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 _id: salsa::Id,
182 use_id: UseId<'db>,
183) -> Maybe<Arc<UseData<'db>>> {
184 let module_id = use_id.parent_module(db);
185 let mut diagnostics = SemanticDiagnostics::new(module_id);
186 let use_ast = db.module_use_by_id(use_id)?;
187 let err = Err(diagnostics.report(use_ast.stable_ptr(db), UseCycle));
188 let inference_id =
189 InferenceId::LookupItemDeclaration(LookupItemId::ModuleItem(ModuleItemId::Use(use_id)));
190 Ok(Arc::new(UseData {
191 diagnostics: diagnostics.build(),
192 resolved_item: err,
193 resolver_data: Arc::new(ResolverData::new(module_id, inference_id)),
194 }))
195}
196
197fn use_semantic_diagnostics<'db>(
199 db: &'db dyn Database,
200 use_id: UseId<'db>,
201) -> Diagnostics<'db, SemanticDiagnostic<'db>> {
202 db.priv_use_semantic_data(use_id).map(|data| data.diagnostics.clone()).unwrap_or_default()
203}
204
205#[salsa::tracked]
207fn use_semantic_diagnostics_tracked<'db>(
208 db: &'db dyn Database,
209 use_id: UseId<'db>,
210) -> Diagnostics<'db, SemanticDiagnostic<'db>> {
211 use_semantic_diagnostics(db, use_id)
212}
213
214fn use_resolver_data<'db>(
216 db: &'db dyn Database,
217 use_id: UseId<'db>,
218) -> Maybe<Arc<ResolverData<'db>>> {
219 Ok(db.priv_use_semantic_data(use_id)?.resolver_data.clone())
220}
221
222#[salsa::tracked(cycle_result=use_resolver_data_cycle)]
224fn use_resolver_data_tracked<'db>(
225 db: &'db dyn Database,
226 use_id: UseId<'db>,
227) -> Maybe<Arc<ResolverData<'db>>> {
228 use_resolver_data(db, use_id)
229}
230
231fn use_resolver_data_cycle<'db>(
233 db: &'db dyn Database,
234 _id: salsa::Id,
235 use_id: UseId<'db>,
236) -> Maybe<Arc<ResolverData<'db>>> {
237 use_resolver_data(db, use_id)
239}
240
241pub trait SemanticUseEx<'a>: Database {
242 fn use_resolved_item(&'a self, use_id: UseId<'a>) -> Maybe<ResolvedGenericItem<'a>> {
246 let db = self.as_dyn_database();
247 db.priv_use_semantic_data(use_id)?.resolved_item.clone()
248 }
249}
250
251impl<'a, T: Database + ?Sized> SemanticUseEx<'a> for T {}
252
253#[derive(Clone, Debug, PartialEq, Eq, DebugWithDb, salsa::Update)]
254#[debug_db(dyn Database)]
255pub struct UseGlobalData<'db> {
256 diagnostics: Diagnostics<'db, SemanticDiagnostic<'db>>,
257 imported_module: Maybe<ModuleId<'db>>,
258}
259
260fn priv_global_use_semantic_data<'db>(
262 db: &'db dyn Database,
263 global_use_id: GlobalUseId<'db>,
264) -> Maybe<UseGlobalData<'db>> {
265 let module_id = global_use_id.parent_module(db);
266 let mut diagnostics = SemanticDiagnostics::new(module_id);
267 let inference_id = InferenceId::GlobalUseStar(global_use_id);
268 let star_ast = ast::UsePath::Star(db.module_global_use_by_id(global_use_id)?);
269 let mut resolver = Resolver::new(db, module_id, inference_id);
270 let edition = resolver.settings.edition;
271 if edition.ignore_visibility() {
272 diagnostics.report(star_ast.stable_ptr(db), GlobalUsesNotSupportedInEdition(edition));
274 }
275
276 let item = star_ast.get_item(db);
277 let segments = get_use_path_segments(db, star_ast.clone())?;
278 let Some(last_segment) = segments.segments.last() else {
279 let imported_module = Err(diagnostics.report(star_ast.stable_ptr(db), UseStarEmptyPath));
280 return Ok(UseGlobalData { diagnostics: diagnostics.build(), imported_module });
281 };
282 let last_element_ptr = last_segment.stable_ptr(db);
283 resolver.set_feature_config(&global_use_id, &item, &mut diagnostics);
284 let imported_module = resolver
285 .resolve_generic_path(
286 &mut diagnostics,
287 segments,
288 NotFoundItemType::Identifier,
289 ResolutionContext::Default,
290 )
291 .and_then(|resolved_item| match &resolved_item {
292 ResolvedGenericItem::Module(module_id) => Ok(*module_id),
293 other => Err(diagnostics.report(
294 last_element_ptr,
295 UnexpectedElement { expected: vec![ElementKind::Module], actual: other.into() },
296 )),
297 });
298 if imported_module == Ok(module_id) {
299 diagnostics.report(star_ast.stable_ptr(db), SelfGlobalUse);
300 }
301 Ok(UseGlobalData { diagnostics: diagnostics.build(), imported_module })
302}
303
304#[salsa::tracked(cycle_fn=priv_global_use_semantic_data_tracked_cycle_fn, cycle_initial=priv_global_use_semantic_data_tracked_initial)]
306fn priv_global_use_semantic_data_tracked<'db>(
307 db: &'db dyn Database,
308 global_use_id: GlobalUseId<'db>,
309) -> Maybe<UseGlobalData<'db>> {
310 priv_global_use_semantic_data(db, global_use_id)
311}
312
313fn priv_global_use_semantic_data_tracked_cycle_fn<'db>(
315 _db: &'db dyn Database,
316 _cycle: &salsa::Cycle<'_>,
317 _last_provisional_value: &Maybe<UseGlobalData<'db>>,
318 value: Maybe<UseGlobalData<'db>>,
319 _global_use_id: GlobalUseId<'db>,
320) -> Maybe<UseGlobalData<'db>> {
321 value
322}
323
324fn priv_global_use_semantic_data_tracked_initial<'db>(
326 db: &'db dyn Database,
327 _id: salsa::Id,
328 global_use_id: GlobalUseId<'db>,
329) -> Maybe<UseGlobalData<'db>> {
330 let mut diagnostics = SemanticDiagnostics::new(global_use_id.parent_module(db));
331 let global_use_ast = db.module_global_use_by_id(global_use_id)?;
332 let star_ast = ast::UsePath::Star(db.module_global_use_by_id(global_use_id)?);
333 let segments = get_use_path_segments(db, star_ast)?;
334 let err = if segments.segments.len() == 1 {
335 diagnostics.report(
339 segments.segments.last().unwrap().stable_ptr(db),
340 PathNotFound(NotFoundItemType::Identifier),
341 )
342 } else {
343 diagnostics.report(global_use_ast.stable_ptr(db), UseCycle)
344 };
345 Ok(UseGlobalData { diagnostics: diagnostics.build(), imported_module: Err(err) })
346}
347
348fn priv_global_use_imported_module<'db>(
350 db: &'db dyn Database,
351 global_use_id: GlobalUseId<'db>,
352) -> Maybe<ModuleId<'db>> {
353 db.priv_global_use_semantic_data(global_use_id)?.imported_module
354}
355
356#[salsa::tracked]
358fn priv_global_use_imported_module_tracked<'db>(
359 db: &'db dyn Database,
360 global_use_id: GlobalUseId<'db>,
361) -> Maybe<ModuleId<'db>> {
362 priv_global_use_imported_module(db, global_use_id)
363}
364
365fn global_use_semantic_diagnostics<'db>(
367 db: &'db dyn Database,
368 global_use_id: GlobalUseId<'db>,
369) -> Diagnostics<'db, SemanticDiagnostic<'db>> {
370 db.priv_global_use_semantic_data(global_use_id).map(|data| data.diagnostics).unwrap_or_default()
371}
372
373#[salsa::tracked]
375fn global_use_semantic_diagnostics_tracked<'db>(
376 db: &'db dyn Database,
377 global_use_id: GlobalUseId<'db>,
378) -> Diagnostics<'db, SemanticDiagnostic<'db>> {
379 global_use_semantic_diagnostics(db, global_use_id)
380}
381
382pub type ImportedModules<'db> = OrderedHashMap<ModuleId<'db>, ImportInfo<'db>>;
384
385#[derive(Debug, Default, Clone, PartialEq, Eq, salsa::Update)]
387pub struct ImportInfo<'db> {
388 pub user_modules: Vec<ModuleId<'db>>,
390}
391
392#[salsa::tracked(returns(ref),cycle_fn=module_imported_modules_cycle_fn, cycle_initial=module_imported_modules_initial)]
395fn module_imported_modules<'db>(
396 db: &'db dyn Database,
397 _tracked: Tracked,
398 module_id: ModuleId<'db>,
399) -> ImportedModules<'db> {
400 let mut visited = UnorderedHashSet::<_>::default();
401 let mut stack = vec![(module_id, module_id)];
402 let mut modules = OrderedHashMap::<ModuleId<'db>, ImportInfo<'db>>::default();
403 modules.insert(module_id, ImportInfo { user_modules: vec![module_id] });
404 while let Some((user_module, containing_module)) = stack.pop() {
407 if !visited.insert((user_module, containing_module)) {
408 continue;
409 }
410 for defined_module in
411 chain!([&containing_module], module_macro_modules(db, false, containing_module))
412 {
413 let Ok(glob_uses) = get_module_global_uses(db, *defined_module) else {
414 continue;
415 };
416 for (glob_use, item_visibility) in glob_uses.iter() {
417 let Ok(module_id_found) = db.priv_global_use_imported_module(*glob_use) else {
418 continue;
419 };
420 let entry = modules.entry(module_id_found).or_default();
422 if peek_visible_in(db, *item_visibility, containing_module, user_module) {
423 stack.push((containing_module, module_id_found));
424 entry.user_modules.push(containing_module);
425 }
426 }
427 }
428 }
429 let mut stack =
430 modules.iter().filter(|(_, v)| v.user_modules.is_empty()).map(|(k, _)| *k).collect_vec();
431 while let Some(curr_module_id) = stack.pop() {
433 for defined_module in
434 chain!([&curr_module_id], module_macro_modules(db, false, curr_module_id))
435 {
436 let Ok(glob_uses) = get_module_global_uses(db, *defined_module) else { continue };
437 for glob_use in glob_uses.keys() {
438 if let Ok(module_id_found) = db.priv_global_use_imported_module(*glob_use)
439 && let Entry::Vacant(entry) = modules.entry(module_id_found)
440 {
441 entry.insert(ImportInfo::default());
442 stack.push(module_id_found);
443 }
444 }
445 }
446 }
447 modules
448}
449
450fn module_imported_modules_cycle_fn<'db>(
452 _db: &dyn Database,
453 _cycle: &salsa::Cycle<'_>,
454 _last_provisional_value: &ImportedModules<'db>,
455 value: ImportedModules<'db>,
456 _tracked: Tracked,
457 _module_id: ModuleId<'db>,
458) -> ImportedModules<'db> {
459 value
460}
461
462fn module_imported_modules_initial<'db>(
464 _db: &'db dyn Database,
465 _id: salsa::Id,
466 _tracked: Tracked,
467 _module_id: ModuleId<'db>,
468) -> ImportedModules<'db> {
469 Default::default()
470}
471
472pub trait UseSemantic<'db>: Database {
474 fn priv_use_semantic_data(&'db self, use_id: UseId<'db>) -> Maybe<Arc<UseData<'db>>> {
476 priv_use_semantic_data_tracked(self.as_dyn_database(), use_id)
477 }
478 fn use_semantic_diagnostics(
480 &'db self,
481 use_id: UseId<'db>,
482 ) -> Diagnostics<'db, SemanticDiagnostic<'db>> {
483 use_semantic_diagnostics_tracked(self.as_dyn_database(), use_id)
484 }
485 fn use_resolver_data(&'db self, use_id: UseId<'db>) -> Maybe<Arc<ResolverData<'db>>> {
487 use_resolver_data_tracked(self.as_dyn_database(), use_id)
488 }
489 fn priv_global_use_semantic_data(
491 &'db self,
492 global_use_id: GlobalUseId<'db>,
493 ) -> Maybe<UseGlobalData<'db>> {
494 priv_global_use_semantic_data_tracked(self.as_dyn_database(), global_use_id)
495 }
496 fn priv_global_use_imported_module(
498 &'db self,
499 global_use_id: GlobalUseId<'db>,
500 ) -> Maybe<ModuleId<'db>> {
501 priv_global_use_imported_module_tracked(self.as_dyn_database(), global_use_id)
502 }
503 fn global_use_semantic_diagnostics(
505 &'db self,
506 global_use_id: GlobalUseId<'db>,
507 ) -> Diagnostics<'db, SemanticDiagnostic<'db>> {
508 global_use_semantic_diagnostics_tracked(self.as_dyn_database(), global_use_id)
509 }
510 fn module_imported_modules(
512 &'db self,
513 _tracked: Tracked,
514 module_id: ModuleId<'db>,
515 ) -> &'db ImportedModules<'db> {
516 module_imported_modules(self.as_dyn_database(), _tracked, module_id)
517 }
518}
519impl<'db, T: Database + ?Sized> UseSemantic<'db> for T {}