cairo_lang_semantic/
lsp_helpers.rs

1use std::sync::Arc;
2
3use cairo_lang_defs::db::DefsGroup;
4use cairo_lang_defs::ids::{
5    GenericTypeId, ImportableId, LanguageElementId, ModuleId, NamedLanguageElementId,
6    TraitFunctionId, TraitId,
7};
8use cairo_lang_filesystem::db::{CORELIB_CRATE_NAME, FilesGroup, default_crate_settings};
9use cairo_lang_filesystem::ids::{
10    CrateId, CrateLongId, FileId, FileInput, FileLongId, SmolStrId, Tracked,
11};
12use cairo_lang_utils::Intern;
13use cairo_lang_utils::ordered_hash_map::{Entry, OrderedHashMap};
14use cairo_lang_utils::unordered_hash_set::UnorderedHashSet;
15use itertools::chain;
16use salsa::{Accumulator, Database};
17
18use crate::Variant;
19use crate::corelib::{self, core_submodule, get_submodule};
20use crate::db::module_inline_macro_expansions;
21use crate::expr::inference::InferenceId;
22use crate::items::enm::EnumSemantic;
23use crate::items::functions::GenericFunctionId;
24use crate::items::macro_call::module_macro_modules;
25use crate::items::module::ModuleSemantic;
26use crate::items::trt::TraitSemantic;
27use crate::items::us::SemanticUseEx;
28use crate::keyword::SELF_PARAM_KW;
29use crate::resolve::{ResolvedGenericItem, Resolver};
30use crate::types::TypeHead;
31
32/// A filter for types.
33#[derive(Clone, Debug, Hash, PartialEq, Eq)]
34pub enum TypeFilter<'db> {
35    /// No filter is applied.
36    NoFilter,
37    /// Only methods with the given type head are returned.
38    TypeHead(TypeHead<'db>),
39}
40
41/// Implementation of [LspHelpers::methods_in_module].
42pub fn methods_in_module<'db>(
43    db: &'db dyn Database,
44    module_id: ModuleId<'db>,
45    type_filter: TypeFilter<'db>,
46) -> Arc<Vec<TraitFunctionId<'db>>> {
47    let mut result = Vec::new();
48    let Ok(module_traits_ids) = db.module_traits_ids(module_id) else {
49        return result.into();
50    };
51    for trait_id in module_traits_ids.iter().copied() {
52        if let Ok(trait_functions) = db.trait_functions(trait_id) {
53            for trait_function in trait_functions.values() {
54                let Ok(signature) = db.trait_function_signature(*trait_function) else {
55                    continue;
56                };
57                let Some(first_param) = signature.params.first() else {
58                    continue;
59                };
60                if first_param.name.long(db) != SELF_PARAM_KW {
61                    continue;
62                }
63                if let TypeFilter::TypeHead(type_head) = &type_filter
64                    && let Some(head) = first_param.ty.head(db)
65                    && !fit_for_method(&head, type_head)
66                {
67                    continue;
68                }
69
70                result.push(*trait_function)
71            }
72        }
73    }
74    Arc::new(result)
75}
76
77/// Query implementation of [LspHelpers::methods_in_module].
78#[salsa::tracked]
79pub fn methods_in_module_tracked<'db>(
80    db: &'db dyn Database,
81    module_id: ModuleId<'db>,
82    type_filter: TypeFilter<'db>,
83) -> Arc<Vec<TraitFunctionId<'db>>> {
84    methods_in_module(db, module_id, type_filter)
85}
86
87/// Checks if a type head can fit for a method.
88fn fit_for_method(head: &TypeHead<'_>, type_head: &TypeHead<'_>) -> bool {
89    // Allow generics so we can filter them later with resolver.
90    if let TypeHead::Generic(_) = head {
91        return true;
92    }
93    if head == type_head {
94        return true;
95    }
96    if let TypeHead::Snapshot(snapshot_head) = head {
97        return fit_for_method(snapshot_head.as_ref(), type_head);
98    }
99    false
100}
101
102/// Implementation of [LspHelpers::methods_in_crate].
103pub fn methods_in_crate<'db>(
104    db: &'db dyn Database,
105    crate_id: CrateId<'db>,
106    type_filter: TypeFilter<'db>,
107) -> Arc<Vec<TraitFunctionId<'db>>> {
108    let mut result = Vec::new();
109    for module_id in db.crate_modules(crate_id).iter() {
110        result.extend_from_slice(&db.methods_in_module(*module_id, type_filter.clone())[..])
111    }
112    Arc::new(result)
113}
114
115/// Query implementation of [LspHelpers::methods_in_crate].
116#[salsa::tracked]
117pub fn methods_in_crate_tracked<'db>(
118    db: &'db dyn Database,
119    crate_id: CrateId<'db>,
120    type_filter: TypeFilter<'db>,
121) -> Arc<Vec<TraitFunctionId<'db>>> {
122    methods_in_crate(db, crate_id, type_filter)
123}
124
125/// Implementation of [LspHelpers::visible_importables_in_module].
126pub fn visible_importables_in_module<'db>(
127    db: &'db dyn Database,
128    module_id: ModuleId<'db>,
129    user_module_id: ModuleId<'db>,
130    include_parent: bool,
131) -> Arc<Vec<(ImportableId<'db>, String)>> {
132    let mut visited_modules = UnorderedHashSet::default();
133    visible_importables_in_module_ex(
134        db,
135        module_id,
136        user_module_id,
137        include_parent,
138        &mut visited_modules,
139    )
140    .unwrap_or_else(|| Arc::new(Vec::new()))
141}
142
143/// Query implementation of [LspHelpers::visible_importables_in_module].
144#[salsa::tracked]
145pub fn visible_importables_in_module_tracked<'db>(
146    db: &'db dyn Database,
147    module_id: ModuleId<'db>,
148    user_module_id: ModuleId<'db>,
149    include_parent: bool,
150) -> Arc<Vec<(ImportableId<'db>, String)>> {
151    visible_importables_in_module(db, module_id, user_module_id, include_parent)
152}
153
154/// Returns the visible importables in a module, including the importables in the parent module if
155/// needed. The visibility is relative to the module `user_module_id`.
156fn visible_importables_in_module_ex<'db>(
157    db: &'db dyn Database,
158    module_id: ModuleId<'db>,
159    user_module_id: ModuleId<'db>,
160    include_parent: bool,
161    visited_modules: &mut UnorderedHashSet<ModuleId<'db>>,
162) -> Option<Arc<Vec<(ImportableId<'db>, String)>>> {
163    let mut result = Vec::new();
164    if visited_modules.contains(&module_id) {
165        return Some(result.into());
166    }
167
168    let resolver = Resolver::new(db, user_module_id, InferenceId::NoContext);
169
170    // Check if an item in the current module is visible from the user module.
171    let is_visible = |item_name: SmolStrId<'_>| {
172        let item_info = db.module_item_info_by_name(module_id, item_name).ok()??;
173        Some(resolver.is_item_visible(module_id, &item_info, user_module_id))
174    };
175    visited_modules.insert(module_id);
176    let mut modules_to_visit = vec![];
177    // Add importables and traverse modules imported into the current module.
178    for use_id in db.module_uses_ids(module_id).unwrap_or_default().iter().copied() {
179        if !is_visible(use_id.name(db)).unwrap_or_default() {
180            continue;
181        }
182        let Ok(resolved_item) = db.use_resolved_item(use_id) else {
183            continue;
184        };
185
186        let (resolved_item, name) = match resolved_item {
187            ResolvedGenericItem::Module(ModuleId::CrateRoot(crate_id)) => {
188                result.extend_from_slice(&db.visible_importables_in_crate(crate_id, module_id)[..]);
189
190                (ImportableId::Crate(crate_id), crate_id.long(db).name().long(db))
191            }
192            ResolvedGenericItem::Module(inner_module_id @ ModuleId::Submodule(module)) => {
193                modules_to_visit.push(inner_module_id);
194
195                (ImportableId::Submodule(module), module.name(db).long(db))
196            }
197            ResolvedGenericItem::Module(ModuleId::MacroCall { .. }) => {
198                continue;
199            }
200            ResolvedGenericItem::GenericConstant(item_id) => {
201                (ImportableId::Constant(item_id), item_id.name(db).long(db))
202            }
203            ResolvedGenericItem::GenericFunction(GenericFunctionId::Free(item_id)) => {
204                (ImportableId::FreeFunction(item_id), item_id.name(db).long(db))
205            }
206            ResolvedGenericItem::GenericFunction(GenericFunctionId::Extern(item_id)) => {
207                (ImportableId::ExternFunction(item_id), item_id.name(db).long(db))
208            }
209            ResolvedGenericItem::GenericType(GenericTypeId::Struct(item_id)) => {
210                (ImportableId::Struct(item_id), item_id.name(db).long(db))
211            }
212            ResolvedGenericItem::GenericType(GenericTypeId::Enum(item_id)) => {
213                let enum_name = item_id.name(db);
214
215                if let Ok(variants) = db.enum_variants(item_id) {
216                    for (name, id) in variants.iter() {
217                        result.push((
218                            ImportableId::Variant(*id),
219                            format!("{}::{}", enum_name.long(db), name.long(db)),
220                        ));
221                    }
222                }
223
224                (ImportableId::Enum(item_id), enum_name.long(db))
225            }
226            ResolvedGenericItem::GenericType(GenericTypeId::Extern(item_id)) => {
227                (ImportableId::ExternType(item_id), item_id.name(db).long(db))
228            }
229            ResolvedGenericItem::GenericTypeAlias(item_id) => {
230                (ImportableId::TypeAlias(item_id), item_id.name(db).long(db))
231            }
232            ResolvedGenericItem::GenericImplAlias(item_id) => {
233                (ImportableId::ImplAlias(item_id), item_id.name(db).long(db))
234            }
235            ResolvedGenericItem::Variant(Variant { id, .. }) => {
236                (ImportableId::Variant(id), id.name(db).long(db))
237            }
238            ResolvedGenericItem::Trait(item_id) => {
239                (ImportableId::Trait(item_id), item_id.name(db).long(db))
240            }
241            ResolvedGenericItem::Impl(item_id) => {
242                (ImportableId::Impl(item_id), item_id.name(db).long(db))
243            }
244            ResolvedGenericItem::Macro(item_id) => {
245                (ImportableId::MacroDeclaration(item_id), item_id.name(db).long(db))
246            }
247            ResolvedGenericItem::Variable(_)
248            | ResolvedGenericItem::TraitItem(_)
249            | ResolvedGenericItem::GenericFunction(GenericFunctionId::Impl(_)) => continue,
250        };
251
252        result.push((resolved_item, name.to_string()));
253    }
254
255    if !matches!(module_id, ModuleId::MacroCall { .. }) {
256        modules_to_visit.extend(module_macro_modules(db, false, module_id).iter().copied());
257    }
258
259    for submodule_id in db.module_submodules_ids(module_id).unwrap_or_default().iter().copied() {
260        if !is_visible(submodule_id.name(db)).unwrap_or_default() {
261            continue;
262        }
263        result.push((ImportableId::Submodule(submodule_id), submodule_id.name(db).to_string(db)));
264        modules_to_visit.push(ModuleId::Submodule(submodule_id));
265    }
266
267    // Handle enums separately because we need to include their variants.
268    for enum_id in db.module_enums_ids(module_id).unwrap_or_default().iter().copied() {
269        let enum_name = enum_id.name(db);
270        if !is_visible(enum_name).unwrap_or_default() {
271            continue;
272        }
273
274        result.push((ImportableId::Enum(enum_id), enum_name.to_string(db)));
275
276        if let Ok(variants) = db.enum_variants(enum_id) {
277            for (name, id) in variants.iter() {
278                result.push((
279                    ImportableId::Variant(*id),
280                    format!("{}::{}", enum_name.long(db), name.long(db)),
281                ));
282            }
283        }
284    }
285
286    macro_rules! module_importables {
287        ($query:ident, $map:expr) => {
288            for item_id in db.$query(module_id).ok().unwrap_or_default().iter().copied() {
289                if !is_visible(item_id.name(db)).unwrap_or_default() {
290                    continue;
291                }
292                result.push(($map(item_id), item_id.name(db).to_string(db)));
293            }
294        };
295    }
296
297    module_importables!(module_constants_ids, ImportableId::Constant);
298    module_importables!(module_free_functions_ids, ImportableId::FreeFunction);
299    module_importables!(module_structs_ids, ImportableId::Struct);
300    module_importables!(module_type_aliases_ids, ImportableId::TypeAlias);
301    module_importables!(module_impl_aliases_ids, ImportableId::ImplAlias);
302    module_importables!(module_traits_ids, ImportableId::Trait);
303    module_importables!(module_impls_ids, ImportableId::Impl);
304    module_importables!(module_extern_functions_ids, ImportableId::ExternFunction);
305    module_importables!(module_extern_types_ids, ImportableId::ExternType);
306    module_importables!(module_macro_declarations_ids, ImportableId::MacroDeclaration);
307
308    for submodule in modules_to_visit {
309        for (item_id, path) in
310            visible_importables_in_module_ex(db, submodule, user_module_id, false, visited_modules)
311                .unwrap_or_default()
312                .iter()
313        {
314            result.push((*item_id, format!("{}::{}", submodule.name(db).long(db), path)));
315        }
316    }
317    // Traverse the parent module if needed.
318    if include_parent {
319        match module_id {
320            ModuleId::CrateRoot(_) => {}
321            ModuleId::Submodule(submodule_id) => {
322                let parent_module_id = submodule_id.parent_module(db);
323                for (item_id, path) in visible_importables_in_module_ex(
324                    db,
325                    parent_module_id,
326                    user_module_id,
327                    include_parent,
328                    visited_modules,
329                )
330                .unwrap_or_default()
331                .iter()
332                {
333                    result.push((*item_id, format!("super::{path}")));
334                }
335            }
336            ModuleId::MacroCall { .. } => {}
337        }
338    }
339    Some(Arc::new(result))
340}
341
342/// Implementation of [LspHelpers::visible_importables_in_crate].
343pub fn visible_importables_in_crate<'db>(
344    db: &'db dyn Database,
345    crate_id: CrateId<'db>,
346    user_module_id: ModuleId<'db>,
347) -> Arc<Vec<(ImportableId<'db>, String)>> {
348    let is_current_crate = user_module_id.owning_crate(db) == crate_id;
349    let crate_name = if is_current_crate { "crate" } else { crate_id.long(db).name().long(db) };
350    let crate_as_module = ModuleId::CrateRoot(crate_id);
351    db.visible_importables_in_module(crate_as_module, user_module_id, false)
352        .iter()
353        .map(|(item_id, path)| (*item_id, format!("{crate_name}::{path}")))
354        .collect::<Vec<_>>()
355        .into()
356}
357
358/// Query implementation of [LspHelpers::visible_importables_in_crate].
359#[salsa::tracked]
360pub fn visible_importables_in_crate_tracked<'db>(
361    db: &'db dyn Database,
362    crate_id: CrateId<'db>,
363    user_module_id: ModuleId<'db>,
364) -> Arc<Vec<(ImportableId<'db>, String)>> {
365    visible_importables_in_crate(db, crate_id, user_module_id)
366}
367
368/// Implementation of [LspHelpers::visible_importables_from_module].
369pub fn visible_importables_from_module<'db>(
370    db: &'db dyn Database,
371    module_id: ModuleId<'db>,
372) -> Option<Arc<OrderedHashMap<ImportableId<'db>, String>>> {
373    let current_crate_id = module_id.owning_crate(db);
374    let prelude_submodule_name =
375        db.crate_config(current_crate_id)?.settings.edition.prelude_submodule_name(db);
376    let core_prelude_submodule = core_submodule(db, SmolStrId::from(db, "prelude"));
377    let prelude_submodule = get_submodule(db, core_prelude_submodule, prelude_submodule_name)?;
378
379    let mut module_visible_importables = Vec::new();
380    // Collect importables from the prelude.
381    module_visible_importables.extend_from_slice(
382        &db.visible_importables_in_module(prelude_submodule, prelude_submodule, false)[..],
383    );
384    // Collect importables from all dependency crates, including the current crate and corelib.
385    let settings = db
386        .crate_config(current_crate_id)
387        .map(|c| &c.settings)
388        .unwrap_or_else(|| default_crate_settings(db));
389    for crate_id in chain!(
390        [current_crate_id],
391        (!settings.dependencies.contains_key(CORELIB_CRATE_NAME)).then(|| corelib::core_crate(db)),
392        settings.dependencies.iter().map(|(name, setting)| {
393            CrateLongId::Real {
394                name: SmolStrId::from(db, name.clone()),
395                discriminator: setting.discriminator.clone(),
396            }
397            .intern(db)
398        })
399    ) {
400        module_visible_importables
401            .extend_from_slice(&db.visible_importables_in_crate(crate_id, module_id)[..]);
402        module_visible_importables
403            .push((ImportableId::Crate(crate_id), crate_id.long(db).name().to_string(db)));
404    }
405
406    // Collect importables visible in the current module.
407    module_visible_importables
408        .extend_from_slice(&db.visible_importables_in_module(module_id, module_id, true)[..]);
409
410    // Deduplicate importables, preferring shorter paths.
411    // This is the reason for searching in the crates before the current module - to prioritize
412    // shorter, canonical paths prefixed with `crate::` over paths using `super::` or local
413    // imports.
414    let mut result: OrderedHashMap<ImportableId<'_>, String> = OrderedHashMap::default();
415    for (trait_id, path) in module_visible_importables {
416        match result.entry(trait_id) {
417            Entry::Occupied(existing_path) => {
418                if path.split("::").count() < existing_path.get().split("::").count() {
419                    *existing_path.into_mut() = path;
420                }
421            }
422            Entry::Vacant(entry) => {
423                entry.insert(path);
424            }
425        }
426    }
427    Some(Arc::new(result))
428}
429
430/// Query implementation of [LspHelpers::visible_importables_from_module].
431pub fn visible_importables_from_module_tracked<'db>(
432    db: &'db dyn Database,
433    module_id: ModuleId<'db>,
434) -> Option<Arc<OrderedHashMap<ImportableId<'db>, String>>> {
435    visible_importables_from_module_helper(db, (), module_id)
436}
437
438#[salsa::tracked]
439fn visible_importables_from_module_helper<'db>(
440    db: &'db dyn Database,
441    _tracked: Tracked,
442    module_id: ModuleId<'db>,
443) -> Option<Arc<OrderedHashMap<ImportableId<'db>, String>>> {
444    visible_importables_from_module(db, module_id)
445}
446
447/// Implementation of [LspHelpers::visible_traits_from_module].
448pub fn visible_traits_from_module<'db>(
449    db: &'db dyn Database,
450    module_id: ModuleId<'db>,
451) -> Option<Arc<OrderedHashMap<TraitId<'db>, String>>> {
452    let importables = db.visible_importables_from_module(module_id)?;
453
454    let traits = importables
455        .iter()
456        .filter_map(|(item, path)| {
457            if let ImportableId::Trait(trait_id) = item {
458                Some((*trait_id, path.clone()))
459            } else {
460                None
461            }
462        })
463        .collect::<OrderedHashMap<_, _>>()
464        .into();
465
466    Some(traits)
467}
468
469/// Query implementation of [LspHelpers::visible_traits_from_module].
470fn visible_traits_from_module_tracked<'db>(
471    db: &'db dyn Database,
472    module_id: ModuleId<'db>,
473) -> Option<Arc<OrderedHashMap<TraitId<'db>, String>>> {
474    visible_traits_from_module_helper(db, (), module_id)
475}
476
477#[salsa::tracked]
478fn visible_traits_from_module_helper<'db>(
479    db: &'db dyn Database,
480    _tracked: Tracked,
481    module_id: ModuleId<'db>,
482) -> Option<Arc<OrderedHashMap<TraitId<'db>, String>>> {
483    visible_traits_from_module(db, module_id)
484}
485
486/// Query implementation of [LspHelpers::inline_macro_expansion_files].
487#[salsa::tracked(returns(ref))]
488fn inline_macro_expansion_files_tracked<'db>(
489    db: &'db dyn Database,
490    _tracked: Tracked,
491    module_id: ModuleId<'db>,
492) -> Vec<FileId<'db>> {
493    module_inline_macro_expansions(db, module_id)
494        .iter()
495        .map(|expansion| expansion.file.clone().into_file_long_id(db).intern(db))
496        .collect()
497}
498
499#[salsa::accumulator]
500pub struct InlineMacroExpansionAccumulator {
501    file: FileInput,
502}
503
504/// Trait for LSP helpers.
505pub trait LspHelpers<'db>: Database {
506    /// Returns all methods in a module that match the given type filter.
507    fn methods_in_module(
508        &'db self,
509        module_id: ModuleId<'db>,
510        type_filter: TypeFilter<'db>,
511    ) -> Arc<Vec<TraitFunctionId<'db>>> {
512        methods_in_module_tracked(self.as_dyn_database(), module_id, type_filter)
513    }
514    /// Returns all methods in a crate that match the given type filter.
515    fn methods_in_crate(
516        &'db self,
517        crate_id: CrateId<'db>,
518        type_filter: TypeFilter<'db>,
519    ) -> Arc<Vec<TraitFunctionId<'db>>> {
520        methods_in_crate_tracked(self.as_dyn_database(), crate_id, type_filter)
521    }
522    /// Returns all the importables visible from a module, alongside a visible use path to the
523    /// trait.
524    fn visible_importables_from_module(
525        &'db self,
526        module_id: ModuleId<'db>,
527    ) -> Option<Arc<OrderedHashMap<ImportableId<'db>, String>>> {
528        visible_importables_from_module_tracked(self.as_dyn_database(), module_id)
529    }
530    /// Returns all visible importables in a module, alongside a visible use path to the trait.
531    /// `user_module_id` is the module from which the importables should be visible. If
532    /// `include_parent` is true, the parent module of `module_id` is also considered.
533    fn visible_importables_in_module(
534        &'db self,
535        module_id: ModuleId<'db>,
536        user_module_id: ModuleId<'db>,
537        include_parent: bool,
538    ) -> Arc<Vec<(ImportableId<'db>, String)>> {
539        visible_importables_in_module_tracked(
540            self.as_dyn_database(),
541            module_id,
542            user_module_id,
543            include_parent,
544        )
545    }
546    /// Returns all visible importables in a crate, alongside a visible use path to the trait.
547    /// `user_module_id` is the module from which the importables should be visible.
548    fn visible_importables_in_crate(
549        &'db self,
550        crate_id: CrateId<'db>,
551        user_module_id: ModuleId<'db>,
552    ) -> Arc<Vec<(ImportableId<'db>, String)>> {
553        visible_importables_in_crate_tracked(self.as_dyn_database(), crate_id, user_module_id)
554    }
555    /// Returns all the traits visible from a module, alongside a visible use path to the trait.
556    fn visible_traits_from_module(
557        &'db self,
558        module_id: ModuleId<'db>,
559    ) -> Option<Arc<OrderedHashMap<TraitId<'db>, String>>> {
560        visible_traits_from_module_tracked(self.as_dyn_database(), module_id)
561    }
562    /// Returns all files generated by inline macro expansion inside provided module.
563    fn inline_macro_expansion_files(&'db self, module_id: ModuleId<'db>) -> &'db Vec<FileId<'db>> {
564        inline_macro_expansion_files_tracked(self.as_dyn_database(), (), module_id)
565    }
566    /// Marks file as result of inline macro expansion.
567    fn accumulate_inline_macro_expansion(&'db self, file: &FileLongId<'db>) {
568        let db = self.as_dyn_database();
569        InlineMacroExpansionAccumulator { file: file.into_file_input(db) }.accumulate(db);
570    }
571}
572impl<'db, T: Database + ?Sized> LspHelpers<'db> for T {}