1use std::sync::Arc;
2
3use cairo_lang_defs::db::DefsGroup;
4use cairo_lang_defs::ids::{
5 GenericTypeId, ImportableId, LanguageElementId, ModuleId, ModuleItemId, NamedLanguageElementId,
6 TraitFunctionId, TraitId,
7};
8use cairo_lang_filesystem::db::{
9 CORELIB_CRATE_NAME, FilesGroup, default_crate_settings, ext_as_virtual,
10};
11use cairo_lang_filesystem::ids::{CrateId, CrateLongId, FileId, FileLongId, SmolStrId, Tracked};
12use cairo_lang_syntax::node::ast::ItemModule;
13use cairo_lang_syntax::node::helpers::GetIdentifier;
14use cairo_lang_syntax::node::{SyntaxNode, TypedSyntaxNode};
15use cairo_lang_utils::Intern;
16use cairo_lang_utils::ordered_hash_map::{Entry, OrderedHashMap};
17use cairo_lang_utils::unordered_hash_set::UnorderedHashSet;
18use itertools::chain;
19use salsa::Database;
20
21use crate::expr::inference::InferenceId;
22use crate::items::constant::ConstantSemantic;
23use crate::items::enm::EnumSemantic;
24use crate::items::free_function::FreeFunctionSemantic;
25use crate::items::functions::GenericFunctionId;
26use crate::items::imp::ImplSemantic;
27use crate::items::macro_call::{MacroCallSemantic, module_macro_modules};
28use crate::items::module::ModuleSemantic;
29use crate::items::module_type_alias::ModuleTypeAliasSemantic;
30use crate::items::trt::TraitSemantic;
31use crate::items::us::SemanticUseEx;
32use crate::keyword::SELF_PARAM_KW;
33use crate::resolve::{ResolvedGenericItem, Resolver};
34use crate::types::TypeHead;
35use crate::{Variant, corelib};
36
37#[derive(Clone, Debug, Hash, PartialEq, Eq)]
39pub enum TypeFilter<'db> {
40 NoFilter,
42 TypeHead(TypeHead<'db>),
44}
45
46pub fn methods_in_module<'db>(
48 db: &'db dyn Database,
49 module_id: ModuleId<'db>,
50 type_filter: TypeFilter<'db>,
51) -> Arc<Vec<TraitFunctionId<'db>>> {
52 let mut result = Vec::new();
53 let Ok(module_traits_ids) = db.module_traits_ids(module_id) else {
54 return result.into();
55 };
56 for trait_id in module_traits_ids.iter().copied() {
57 if let Ok(trait_functions) = db.trait_functions(trait_id) {
58 for trait_function in trait_functions.values() {
59 let Ok(signature) = db.trait_function_signature(*trait_function) else {
60 continue;
61 };
62 let Some(first_param) = signature.params.first() else {
63 continue;
64 };
65 if first_param.name.long(db) != SELF_PARAM_KW {
66 continue;
67 }
68 if let TypeFilter::TypeHead(type_head) = &type_filter
69 && let Some(head) = first_param.ty.head(db)
70 && !fit_for_method(&head, type_head)
71 {
72 continue;
73 }
74
75 result.push(*trait_function)
76 }
77 }
78 }
79 Arc::new(result)
80}
81
82#[salsa::tracked]
84pub fn methods_in_module_tracked<'db>(
85 db: &'db dyn Database,
86 module_id: ModuleId<'db>,
87 type_filter: TypeFilter<'db>,
88) -> Arc<Vec<TraitFunctionId<'db>>> {
89 methods_in_module(db, module_id, type_filter)
90}
91
92fn fit_for_method(head: &TypeHead<'_>, type_head: &TypeHead<'_>) -> bool {
94 if let TypeHead::Generic(_) = head {
96 return true;
97 }
98 if head == type_head {
99 return true;
100 }
101 if let TypeHead::Snapshot(snapshot_head) = head {
102 return fit_for_method(snapshot_head.as_ref(), type_head);
103 }
104 false
105}
106
107pub fn methods_in_crate<'db>(
109 db: &'db dyn Database,
110 crate_id: CrateId<'db>,
111 type_filter: TypeFilter<'db>,
112) -> Arc<Vec<TraitFunctionId<'db>>> {
113 let mut result = Vec::new();
114 for module_id in db.crate_modules(crate_id).iter() {
115 result.extend_from_slice(&db.methods_in_module(*module_id, type_filter.clone())[..])
116 }
117 Arc::new(result)
118}
119
120#[salsa::tracked]
122pub fn methods_in_crate_tracked<'db>(
123 db: &'db dyn Database,
124 crate_id: CrateId<'db>,
125 type_filter: TypeFilter<'db>,
126) -> Arc<Vec<TraitFunctionId<'db>>> {
127 methods_in_crate(db, crate_id, type_filter)
128}
129
130pub fn visible_importables_in_module<'db>(
132 db: &'db dyn Database,
133 module_id: ModuleId<'db>,
134 user_module_id: ModuleId<'db>,
135 include_parent: bool,
136) -> Arc<Vec<(ImportableId<'db>, String)>> {
137 let mut visited_modules = UnorderedHashSet::default();
138 visible_importables_in_module_ex(
139 db,
140 module_id,
141 user_module_id,
142 include_parent,
143 &mut visited_modules,
144 )
145 .unwrap_or_else(|| Arc::new(Vec::new()))
146}
147
148#[salsa::tracked]
150pub fn visible_importables_in_module_tracked<'db>(
151 db: &'db dyn Database,
152 module_id: ModuleId<'db>,
153 user_module_id: ModuleId<'db>,
154 include_parent: bool,
155) -> Arc<Vec<(ImportableId<'db>, String)>> {
156 visible_importables_in_module(db, module_id, user_module_id, include_parent)
157}
158
159fn visible_importables_in_module_ex<'db>(
162 db: &'db dyn Database,
163 module_id: ModuleId<'db>,
164 user_module_id: ModuleId<'db>,
165 include_parent: bool,
166 visited_modules: &mut UnorderedHashSet<ModuleId<'db>>,
167) -> Option<Arc<Vec<(ImportableId<'db>, String)>>> {
168 let mut result = Vec::new();
169 if visited_modules.contains(&module_id) {
170 return Some(result.into());
171 }
172
173 let resolver = Resolver::new(db, user_module_id, InferenceId::NoContext);
174
175 let is_visible = |item_name: SmolStrId<'_>| {
177 let item_info = db.module_item_info_by_name(module_id, item_name).ok()??;
178 Some(resolver.is_item_visible(module_id, &item_info, user_module_id))
179 };
180 visited_modules.insert(module_id);
181 let mut modules_to_visit = vec![];
182 for use_id in db.module_uses_ids(module_id).unwrap_or_default().iter().copied() {
184 if !is_visible(use_id.name(db)).unwrap_or_default() {
185 continue;
186 }
187 let Ok(resolved_item) = db.use_resolved_item(use_id) else {
188 continue;
189 };
190
191 let (resolved_item, name) = match resolved_item {
192 ResolvedGenericItem::Module(ModuleId::CrateRoot(crate_id)) => {
193 result.extend_from_slice(&db.visible_importables_in_crate(crate_id, module_id)[..]);
194
195 (ImportableId::Crate(crate_id), crate_id.long(db).name().long(db))
196 }
197 ResolvedGenericItem::Module(inner_module_id @ ModuleId::Submodule(module)) => {
198 modules_to_visit.push(inner_module_id);
199
200 (ImportableId::Submodule(module), module.name(db).long(db))
201 }
202 ResolvedGenericItem::Module(ModuleId::MacroCall { .. }) => {
203 continue;
204 }
205 ResolvedGenericItem::GenericConstant(item_id) => {
206 (ImportableId::Constant(item_id), item_id.name(db).long(db))
207 }
208 ResolvedGenericItem::GenericFunction(GenericFunctionId::Free(item_id)) => {
209 (ImportableId::FreeFunction(item_id), item_id.name(db).long(db))
210 }
211 ResolvedGenericItem::GenericFunction(GenericFunctionId::Extern(item_id)) => {
212 (ImportableId::ExternFunction(item_id), item_id.name(db).long(db))
213 }
214 ResolvedGenericItem::GenericType(GenericTypeId::Struct(item_id)) => {
215 (ImportableId::Struct(item_id), item_id.name(db).long(db))
216 }
217 ResolvedGenericItem::GenericType(GenericTypeId::Enum(item_id)) => {
218 let enum_name = item_id.name(db);
219
220 if let Ok(variants) = db.enum_variants(item_id) {
221 for (name, id) in variants.iter() {
222 result.push((
223 ImportableId::Variant(*id),
224 format!("{}::{}", enum_name.long(db), name.long(db)),
225 ));
226 }
227 }
228
229 (ImportableId::Enum(item_id), enum_name.long(db))
230 }
231 ResolvedGenericItem::GenericType(GenericTypeId::Extern(item_id)) => {
232 (ImportableId::ExternType(item_id), item_id.name(db).long(db))
233 }
234 ResolvedGenericItem::GenericTypeAlias(item_id) => {
235 (ImportableId::TypeAlias(item_id), item_id.name(db).long(db))
236 }
237 ResolvedGenericItem::GenericImplAlias(item_id) => {
238 (ImportableId::ImplAlias(item_id), item_id.name(db).long(db))
239 }
240 ResolvedGenericItem::Variant(Variant { id, .. }) => {
241 (ImportableId::Variant(id), id.name(db).long(db))
242 }
243 ResolvedGenericItem::Trait(item_id) => {
244 (ImportableId::Trait(item_id), item_id.name(db).long(db))
245 }
246 ResolvedGenericItem::Impl(item_id) => {
247 (ImportableId::Impl(item_id), item_id.name(db).long(db))
248 }
249 ResolvedGenericItem::Macro(item_id) => {
250 (ImportableId::MacroDeclaration(item_id), item_id.name(db).long(db))
251 }
252 ResolvedGenericItem::Variable(_)
253 | ResolvedGenericItem::TraitItem(_)
254 | ResolvedGenericItem::GenericFunction(GenericFunctionId::Impl(_)) => continue,
255 };
256
257 result.push((resolved_item, name.to_string()));
258 }
259
260 if !matches!(module_id, ModuleId::MacroCall { .. }) {
261 modules_to_visit.extend(module_macro_modules(db, false, module_id).iter().copied());
262 }
263
264 for submodule_id in db.module_submodules_ids(module_id).unwrap_or_default().iter().copied() {
265 if !is_visible(submodule_id.name(db)).unwrap_or_default() {
266 continue;
267 }
268 result.push((ImportableId::Submodule(submodule_id), submodule_id.name(db).to_string(db)));
269 modules_to_visit.push(ModuleId::Submodule(submodule_id));
270 }
271
272 for enum_id in db.module_enums_ids(module_id).unwrap_or_default().iter().copied() {
274 let enum_name = enum_id.name(db);
275 if !is_visible(enum_name).unwrap_or_default() {
276 continue;
277 }
278
279 result.push((ImportableId::Enum(enum_id), enum_name.to_string(db)));
280
281 if let Ok(variants) = db.enum_variants(enum_id) {
282 for (name, id) in variants.iter() {
283 result.push((
284 ImportableId::Variant(*id),
285 format!("{}::{}", enum_name.long(db), name.long(db)),
286 ));
287 }
288 }
289 }
290
291 macro_rules! module_importables {
292 ($query:ident, $map:expr) => {
293 for item_id in db.$query(module_id).ok().unwrap_or_default().iter().copied() {
294 if !is_visible(item_id.name(db)).unwrap_or_default() {
295 continue;
296 }
297 result.push(($map(item_id), item_id.name(db).to_string(db)));
298 }
299 };
300 }
301
302 module_importables!(module_constants_ids, ImportableId::Constant);
303 module_importables!(module_free_functions_ids, ImportableId::FreeFunction);
304 module_importables!(module_structs_ids, ImportableId::Struct);
305 module_importables!(module_type_aliases_ids, ImportableId::TypeAlias);
306 module_importables!(module_impl_aliases_ids, ImportableId::ImplAlias);
307 module_importables!(module_traits_ids, ImportableId::Trait);
308 module_importables!(module_impls_ids, ImportableId::Impl);
309 module_importables!(module_extern_functions_ids, ImportableId::ExternFunction);
310 module_importables!(module_extern_types_ids, ImportableId::ExternType);
311 module_importables!(module_macro_declarations_ids, ImportableId::MacroDeclaration);
312
313 for submodule in modules_to_visit {
314 for (item_id, path) in
315 visible_importables_in_module_ex(db, submodule, user_module_id, false, visited_modules)
316 .unwrap_or_default()
317 .iter()
318 {
319 result.push((*item_id, format!("{}::{}", submodule.name(db).long(db), path)));
320 }
321 }
322 if include_parent {
324 match module_id {
325 ModuleId::CrateRoot(_) => {}
326 ModuleId::Submodule(submodule_id) => {
327 let parent_module_id = submodule_id.parent_module(db);
328 for (item_id, path) in visible_importables_in_module_ex(
329 db,
330 parent_module_id,
331 user_module_id,
332 include_parent,
333 visited_modules,
334 )
335 .unwrap_or_default()
336 .iter()
337 {
338 result.push((*item_id, format!("super::{path}")));
339 }
340 }
341 ModuleId::MacroCall { .. } => {}
342 }
343 }
344 Some(Arc::new(result))
345}
346
347pub fn visible_importables_in_crate<'db>(
349 db: &'db dyn Database,
350 crate_id: CrateId<'db>,
351 user_module_id: ModuleId<'db>,
352) -> Arc<Vec<(ImportableId<'db>, String)>> {
353 let is_current_crate = user_module_id.owning_crate(db) == crate_id;
354 let crate_name = if is_current_crate { "crate" } else { crate_id.long(db).name().long(db) };
355 let crate_as_module = ModuleId::CrateRoot(crate_id);
356 db.visible_importables_in_module(crate_as_module, user_module_id, false)
357 .iter()
358 .map(|(item_id, path)| (*item_id, format!("{crate_name}::{path}")))
359 .collect::<Vec<_>>()
360 .into()
361}
362
363#[salsa::tracked]
365pub fn visible_importables_in_crate_tracked<'db>(
366 db: &'db dyn Database,
367 crate_id: CrateId<'db>,
368 user_module_id: ModuleId<'db>,
369) -> Arc<Vec<(ImportableId<'db>, String)>> {
370 visible_importables_in_crate(db, crate_id, user_module_id)
371}
372
373pub fn visible_importables_from_module<'db>(
375 db: &'db dyn Database,
376 module_id: ModuleId<'db>,
377) -> Option<Arc<OrderedHashMap<ImportableId<'db>, String>>> {
378 let current_crate_id = module_id.owning_crate(db);
379 let prelude_submodule =
380 db.get_prelude_submodule(&db.crate_config(current_crate_id)?.settings)?;
381
382 let mut module_visible_importables = Vec::new();
383 module_visible_importables.extend_from_slice(
385 &db.visible_importables_in_module(prelude_submodule, prelude_submodule, false)[..],
386 );
387 let settings = db
389 .crate_config(current_crate_id)
390 .map(|c| &c.settings)
391 .unwrap_or_else(|| default_crate_settings(db));
392 for crate_id in chain!(
393 [current_crate_id],
394 (!settings.dependencies.contains_key(CORELIB_CRATE_NAME)).then(|| corelib::core_crate(db)),
395 settings.dependencies.iter().map(|(name, setting)| {
396 CrateLongId::Real {
397 name: SmolStrId::from(db, name.clone()),
398 discriminator: setting.discriminator.clone(),
399 }
400 .intern(db)
401 })
402 ) {
403 module_visible_importables
404 .extend_from_slice(&db.visible_importables_in_crate(crate_id, module_id)[..]);
405 module_visible_importables
406 .push((ImportableId::Crate(crate_id), crate_id.long(db).name().to_string(db)));
407 }
408
409 module_visible_importables
411 .extend_from_slice(&db.visible_importables_in_module(module_id, module_id, true)[..]);
412
413 let mut result: OrderedHashMap<ImportableId<'_>, String> = OrderedHashMap::default();
418 for (trait_id, path) in module_visible_importables {
419 match result.entry(trait_id) {
420 Entry::Occupied(existing_path) => {
421 if path.split("::").count() < existing_path.get().split("::").count() {
422 *existing_path.into_mut() = path;
423 }
424 }
425 Entry::Vacant(entry) => {
426 entry.insert(path);
427 }
428 }
429 }
430 Some(Arc::new(result))
431}
432
433pub fn visible_importables_from_module_tracked<'db>(
435 db: &'db dyn Database,
436 module_id: ModuleId<'db>,
437) -> Option<Arc<OrderedHashMap<ImportableId<'db>, String>>> {
438 visible_importables_from_module_helper(db, (), module_id)
439}
440
441#[salsa::tracked]
442fn visible_importables_from_module_helper<'db>(
443 db: &'db dyn Database,
444 _tracked: Tracked,
445 module_id: ModuleId<'db>,
446) -> Option<Arc<OrderedHashMap<ImportableId<'db>, String>>> {
447 visible_importables_from_module(db, module_id)
448}
449
450pub fn visible_traits_from_module<'db>(
452 db: &'db dyn Database,
453 module_id: ModuleId<'db>,
454) -> Option<Arc<OrderedHashMap<TraitId<'db>, String>>> {
455 let importables = db.visible_importables_from_module(module_id)?;
456
457 let traits = importables
458 .iter()
459 .filter_map(|(item, path)| {
460 if let ImportableId::Trait(trait_id) = item {
461 Some((*trait_id, path.clone()))
462 } else {
463 None
464 }
465 })
466 .collect::<OrderedHashMap<_, _>>()
467 .into();
468
469 Some(traits)
470}
471
472fn visible_traits_from_module_tracked<'db>(
474 db: &'db dyn Database,
475 module_id: ModuleId<'db>,
476) -> Option<Arc<OrderedHashMap<TraitId<'db>, String>>> {
477 visible_traits_from_module_helper(db, (), module_id)
478}
479
480#[salsa::tracked]
481fn visible_traits_from_module_helper<'db>(
482 db: &'db dyn Database,
483 _tracked: Tracked,
484 module_id: ModuleId<'db>,
485) -> Option<Arc<OrderedHashMap<TraitId<'db>, String>>> {
486 visible_traits_from_module(db, module_id)
487}
488
489#[salsa::tracked(returns(ref))]
491fn inline_macro_expansion_files_tracked<'db>(
492 db: &'db dyn Database,
493 _tracked: Tracked,
494 module_id: ModuleId<'db>,
495) -> Vec<FileId<'db>> {
496 let mut files = vec![];
497 if let Ok(module_macro_calls_ids) = db.module_macro_calls_ids(module_id) {
498 for macro_call in module_macro_calls_ids {
499 if let Ok(macro_call_data) = db.priv_macro_call_data(*macro_call)
500 && let Ok(ModuleId::MacroCall { generated_file_id, .. }) =
501 macro_call_data.macro_call_module
502 {
503 files.push(generated_file_id);
504 }
505 }
506 }
507 if let Ok(traits) = db.module_traits_ids(module_id) {
508 for trait_id in traits {
509 if let Ok(trait_functions) = db.trait_functions(*trait_id) {
510 for trait_function_id in trait_functions.values() {
511 if let Ok(Some(data)) = db.trait_function_body_resolver_data(*trait_function_id)
512 {
513 files.extend(data.files.iter().copied())
514 }
515 }
516 }
517 }
518 }
519 if let Ok(impls) = db.module_impls_ids(module_id) {
520 for impls_id in impls {
521 if let Ok(impl_functions) = db.impl_functions(*impls_id) {
522 for impl_function_id in impl_functions.values() {
523 if let Ok(data) = db.impl_function_body_resolver_data(*impl_function_id) {
524 files.extend(data.files.iter().copied())
525 }
526 }
527 }
528 }
529 }
530 if let Ok(free_functions) = db.module_free_functions_ids(module_id) {
531 for free_function_id in free_functions {
532 if let Ok(data) = db.free_function_body_resolver_data(*free_function_id) {
533 files.extend(data.files.iter().copied())
534 }
535 }
536 }
537 if let Ok(constants) = db.module_constants_ids(module_id) {
538 for constant_id in constants {
539 if let Ok(data) = db.constant_resolver_data(*constant_id) {
540 files.extend(data.files.iter().copied())
541 }
542 }
543 }
544 if let Ok(type_aliases) = db.module_type_aliases_ids(module_id) {
545 for type_alias_id in type_aliases {
546 if let Ok(data) = db.module_type_alias_resolver_data(*type_alias_id) {
547 files.extend(data.files.iter().copied())
548 }
549 }
550 }
551
552 files
553}
554
555#[salsa::tracked]
556fn find_module_containing_node<'db>(
557 db: &'db dyn Database,
558 _tracked: Tracked,
559 node: SyntaxNode<'db>,
560) -> Option<ModuleId<'db>> {
561 let main_module = {
564 let mut node_file_id = node.stable_ptr(db).file_id(db);
567
568 while db.file_modules(node_file_id).is_err() {
571 node_file_id = match node_file_id.long(db) {
572 FileLongId::OnDisk(_) => return None,
573 FileLongId::External(ext) => ext_as_virtual(db, *ext).parent?,
574 FileLongId::Virtual(vfs) => vfs.parent?,
575 }
576 .file_id;
577 }
578
579 *db.file_modules(node_file_id).ok()?.first()?
581 };
582
583 node.ancestors(db)
586 .filter_map(|node| ItemModule::cast(db, node))
587 .map(|item_module| {
588 item_module
589 .stable_ptr(db)
590 .name_green(db)
591 .identifier(db)
592 })
593 .collect::<Vec<_>>()
595 .into_iter()
596 .try_rfold(main_module, |module, name| {
598 let ModuleItemId::Submodule(submodule) =
599 db.module_item_by_name(module, name).ok()??
600 else {
601 return None;
602 };
603 Some(ModuleId::Submodule(submodule))
604 })
605}
606
607pub trait LspHelpers<'db>: Database {
609 fn methods_in_module(
611 &'db self,
612 module_id: ModuleId<'db>,
613 type_filter: TypeFilter<'db>,
614 ) -> Arc<Vec<TraitFunctionId<'db>>> {
615 methods_in_module_tracked(self.as_dyn_database(), module_id, type_filter)
616 }
617 fn methods_in_crate(
619 &'db self,
620 crate_id: CrateId<'db>,
621 type_filter: TypeFilter<'db>,
622 ) -> Arc<Vec<TraitFunctionId<'db>>> {
623 methods_in_crate_tracked(self.as_dyn_database(), crate_id, type_filter)
624 }
625 fn visible_importables_from_module(
628 &'db self,
629 module_id: ModuleId<'db>,
630 ) -> Option<Arc<OrderedHashMap<ImportableId<'db>, String>>> {
631 visible_importables_from_module_tracked(self.as_dyn_database(), module_id)
632 }
633 fn visible_importables_in_module(
637 &'db self,
638 module_id: ModuleId<'db>,
639 user_module_id: ModuleId<'db>,
640 include_parent: bool,
641 ) -> Arc<Vec<(ImportableId<'db>, String)>> {
642 visible_importables_in_module_tracked(
643 self.as_dyn_database(),
644 module_id,
645 user_module_id,
646 include_parent,
647 )
648 }
649 fn visible_importables_in_crate(
652 &'db self,
653 crate_id: CrateId<'db>,
654 user_module_id: ModuleId<'db>,
655 ) -> Arc<Vec<(ImportableId<'db>, String)>> {
656 visible_importables_in_crate_tracked(self.as_dyn_database(), crate_id, user_module_id)
657 }
658 fn visible_traits_from_module(
660 &'db self,
661 module_id: ModuleId<'db>,
662 ) -> Option<Arc<OrderedHashMap<TraitId<'db>, String>>> {
663 visible_traits_from_module_tracked(self.as_dyn_database(), module_id)
664 }
665 fn inline_macro_expansion_files(&'db self, module_id: ModuleId<'db>) -> &'db Vec<FileId<'db>> {
667 inline_macro_expansion_files_tracked(self.as_dyn_database(), (), module_id)
668 }
669
670 fn find_module_containing_node(&'db self, node: SyntaxNode<'db>) -> Option<ModuleId<'db>> {
672 find_module_containing_node(self.as_dyn_database(), (), node)
673 }
674}
675impl<'db, T: Database + ?Sized> LspHelpers<'db> for T {}