cairo_lang_semantic/
lsp_helpers.rs

1use std::sync::Arc;
2
3use cairo_lang_defs::ids::{
4    FileIndex, GenericTypeId, ImportableId, LanguageElementId, ModuleFileId, ModuleId,
5    NamedLanguageElementId, TraitFunctionId, TraitId,
6};
7use cairo_lang_filesystem::db::CORELIB_CRATE_NAME;
8use cairo_lang_filesystem::ids::{CrateId, CrateLongId};
9use cairo_lang_utils::Intern;
10use cairo_lang_utils::ordered_hash_map::{Entry, OrderedHashMap};
11use cairo_lang_utils::unordered_hash_set::UnorderedHashSet;
12use itertools::chain;
13use smol_str::SmolStr;
14
15use crate::Variant;
16use crate::corelib::{self, core_submodule, get_submodule};
17use crate::db::SemanticGroup;
18use crate::expr::inference::InferenceId;
19use crate::items::functions::GenericFunctionId;
20use crate::items::us::SemanticUseEx;
21use crate::items::visibility::peek_visible_in;
22use crate::resolve::{ResolvedGenericItem, Resolver};
23use crate::types::TypeHead;
24
25/// A filter for types.
26#[derive(Clone, Debug, Hash, PartialEq, Eq)]
27pub enum TypeFilter {
28    /// No filter is applied.
29    NoFilter,
30    /// Only methods with the given type head are returned.
31    TypeHead(TypeHead),
32}
33
34/// Query implementation of [crate::db::SemanticGroup::methods_in_module].
35pub fn methods_in_module(
36    db: &dyn SemanticGroup,
37    module_id: ModuleId,
38    type_filter: TypeFilter,
39) -> Arc<[TraitFunctionId]> {
40    let mut result = Vec::new();
41    let Ok(module_traits_ids) = db.module_traits_ids(module_id) else {
42        return result.into();
43    };
44    for trait_id in module_traits_ids.iter().copied() {
45        for (_, trait_function) in db.trait_functions(trait_id).unwrap_or_default() {
46            let Ok(signature) = db.trait_function_signature(trait_function) else {
47                continue;
48            };
49            let Some(first_param) = signature.params.first() else {
50                continue;
51            };
52            if first_param.name != "self" {
53                continue;
54            }
55            if let TypeFilter::TypeHead(type_head) = &type_filter {
56                if let Some(head) = first_param.ty.head(db) {
57                    if !fit_for_method(&head, type_head) {
58                        continue;
59                    }
60                }
61            }
62
63            result.push(trait_function)
64        }
65    }
66    result.into()
67}
68
69/// Checks if a type head can fit for a method.
70fn fit_for_method(head: &TypeHead, type_head: &TypeHead) -> bool {
71    if head == type_head {
72        return true;
73    }
74    if let TypeHead::Snapshot(snapshot_head) = head {
75        return snapshot_head.as_ref() == type_head;
76    }
77    false
78}
79
80/// Query implementation of [crate::db::SemanticGroup::methods_in_crate].
81pub fn methods_in_crate(
82    db: &dyn SemanticGroup,
83    crate_id: CrateId,
84    type_filter: TypeFilter,
85) -> Arc<[TraitFunctionId]> {
86    let mut result = Vec::new();
87    for module_id in db.crate_modules(crate_id).iter() {
88        result.extend_from_slice(&db.methods_in_module(*module_id, type_filter.clone())[..])
89    }
90    result.into()
91}
92
93/// Query implementation of [crate::db::SemanticGroup::visible_importables_in_module].
94pub fn visible_importables_in_module(
95    db: &dyn SemanticGroup,
96    module_id: ModuleId,
97    user_module_file_id: ModuleFileId,
98    include_parent: bool,
99) -> Arc<[(ImportableId, String)]> {
100    let mut visited_modules = UnorderedHashSet::default();
101    visible_importables_in_module_ex(
102        db,
103        module_id,
104        user_module_file_id,
105        include_parent,
106        &mut visited_modules,
107    )
108    .unwrap_or_else(|| Vec::new().into())
109}
110
111/// Returns the visible importables in a module, including the importables in the parent module if
112/// needed. The visibility is relative to the module `user_module_id`.
113fn visible_importables_in_module_ex(
114    db: &dyn SemanticGroup,
115    module_id: ModuleId,
116    user_module_file_id: ModuleFileId,
117    include_parent: bool,
118    visited_modules: &mut UnorderedHashSet<ModuleId>,
119) -> Option<Arc<[(ImportableId, String)]>> {
120    let mut result = Vec::new();
121    if visited_modules.contains(&module_id) {
122        return Some(result.into());
123    }
124
125    let resolver = Resolver::new(db, user_module_file_id, InferenceId::NoContext);
126    let ignore_visibility = resolver.ignore_visibility_checks(module_id);
127    // Check if an item in the current module is visible from the user module.
128    let is_visible = |item_name: SmolStr| {
129        if ignore_visibility {
130            Some(true)
131        } else {
132            let item_info = db.module_item_info_by_name(module_id, item_name).ok()??;
133            Some(peek_visible_in(
134                db.upcast(),
135                item_info.visibility,
136                module_id,
137                user_module_file_id.0,
138            ))
139        }
140    };
141    visited_modules.insert(module_id);
142    let mut modules_to_visit = vec![];
143    // Add importables and traverse modules imported into the current module.
144    for use_id in db.module_uses_ids(module_id).ok()?.iter().copied() {
145        if !is_visible(use_id.name(db.upcast()))? {
146            continue;
147        }
148        let resolved_item = db.use_resolved_item(use_id).ok()?;
149        let (resolved_item, name) = match resolved_item {
150            ResolvedGenericItem::Module(ModuleId::CrateRoot(_)) => continue,
151            ResolvedGenericItem::Module(inner_module_id @ ModuleId::Submodule(module)) => {
152                modules_to_visit.push(inner_module_id);
153
154                (ImportableId::Submodule(module), module.name(db.upcast()))
155            }
156            ResolvedGenericItem::GenericConstant(item_id) => {
157                (ImportableId::Constant(item_id), item_id.name(db.upcast()))
158            }
159            ResolvedGenericItem::GenericFunction(GenericFunctionId::Free(item_id)) => {
160                (ImportableId::FreeFunction(item_id), item_id.name(db.upcast()))
161            }
162            ResolvedGenericItem::GenericFunction(GenericFunctionId::Extern(item_id)) => {
163                (ImportableId::ExternFunction(item_id), item_id.name(db.upcast()))
164            }
165            ResolvedGenericItem::GenericFunction(GenericFunctionId::Impl(_item_id)) => continue,
166            ResolvedGenericItem::GenericType(GenericTypeId::Struct(item_id)) => {
167                (ImportableId::Struct(item_id), item_id.name(db.upcast()))
168            }
169            ResolvedGenericItem::GenericType(GenericTypeId::Enum(item_id)) => {
170                (ImportableId::Enum(item_id), item_id.name(db.upcast()))
171            }
172            ResolvedGenericItem::GenericType(GenericTypeId::Extern(item_id)) => {
173                (ImportableId::ExternType(item_id), item_id.name(db.upcast()))
174            }
175            ResolvedGenericItem::GenericTypeAlias(item_id) => {
176                (ImportableId::TypeAlias(item_id), item_id.name(db.upcast()))
177            }
178            ResolvedGenericItem::GenericImplAlias(item_id) => {
179                (ImportableId::ImplAlias(item_id), item_id.name(db.upcast()))
180            }
181            ResolvedGenericItem::Variant(Variant { id, .. }) => {
182                (ImportableId::Variant(id), id.name(db.upcast()))
183            }
184            ResolvedGenericItem::Trait(item_id) => {
185                (ImportableId::Trait(item_id), item_id.name(db.upcast()))
186            }
187            ResolvedGenericItem::Impl(item_id) => {
188                (ImportableId::Impl(item_id), item_id.name(db.upcast()))
189            }
190            ResolvedGenericItem::Variable(_) => continue,
191        };
192
193        result.push((resolved_item, name.to_string()));
194    }
195    for submodule_id in db.module_submodules_ids(module_id).ok()?.iter().copied() {
196        if !is_visible(submodule_id.name(db.upcast()))? {
197            continue;
198        }
199        modules_to_visit.push(ModuleId::Submodule(submodule_id));
200    }
201
202    macro_rules! module_importables {
203        ($query:ident, $map:expr) => {
204            for item_id in db.$query(module_id).ok()?.iter().copied() {
205                if !is_visible(item_id.name(db.upcast()))? {
206                    continue;
207                }
208                result.push(($map(item_id), item_id.name(db.upcast()).to_string()));
209            }
210        };
211    }
212
213    module_importables!(module_constants_ids, ImportableId::Constant);
214    module_importables!(module_free_functions_ids, ImportableId::FreeFunction);
215    module_importables!(module_structs_ids, ImportableId::Struct);
216    module_importables!(module_enums_ids, ImportableId::Enum);
217    module_importables!(module_type_aliases_ids, ImportableId::TypeAlias);
218    module_importables!(module_impl_aliases_ids, ImportableId::ImplAlias);
219    module_importables!(module_traits_ids, ImportableId::Trait);
220    module_importables!(module_impls_ids, ImportableId::Impl);
221    module_importables!(module_extern_functions_ids, ImportableId::ExternFunction);
222    module_importables!(module_extern_types_ids, ImportableId::ExternType);
223
224    for submodule in modules_to_visit {
225        for (item_id, path) in visible_importables_in_module_ex(
226            db,
227            submodule,
228            user_module_file_id,
229            false,
230            visited_modules,
231        )?
232        .iter()
233        {
234            result.push((*item_id, format!("{}::{}", submodule.name(db.upcast()), path)));
235        }
236    }
237    // Traverse the parent module if needed.
238    if include_parent {
239        match module_id {
240            ModuleId::CrateRoot(_) => {}
241            ModuleId::Submodule(submodule_id) => {
242                let parent_module_id = submodule_id.parent_module(db.upcast());
243                for (item_id, path) in visible_importables_in_module_ex(
244                    db,
245                    parent_module_id,
246                    user_module_file_id,
247                    include_parent,
248                    visited_modules,
249                )?
250                .iter()
251                {
252                    result.push((*item_id, format!("super::{}", path)));
253                }
254            }
255        }
256    }
257    Some(result.into())
258}
259
260/// Query implementation of [crate::db::SemanticGroup::visible_importables_in_crate].
261pub fn visible_importables_in_crate(
262    db: &dyn SemanticGroup,
263    crate_id: CrateId,
264    user_module_file_id: ModuleFileId,
265) -> Arc<[(ImportableId, String)]> {
266    let is_current_crate = user_module_file_id.0.owning_crate(db.upcast()) == crate_id;
267    let crate_name = if is_current_crate { "crate" } else { &crate_id.name(db.upcast()) };
268    let crate_as_module = ModuleId::CrateRoot(crate_id);
269    db.visible_importables_in_module(crate_as_module, user_module_file_id, false)
270        .iter()
271        .cloned()
272        .map(|(item_id, path)| (item_id, format!("{crate_name}::{path}",)))
273        .collect::<Vec<_>>()
274        .into()
275}
276
277/// Query implementation of [crate::db::SemanticGroup::visible_importables_from_module].
278pub fn visible_importables_from_module(
279    db: &dyn SemanticGroup,
280    module_file_id: ModuleFileId,
281) -> Option<Arc<OrderedHashMap<ImportableId, String>>> {
282    let module_id = module_file_id.0;
283    let mut current_top_module = module_id;
284    while let ModuleId::Submodule(submodule_id) = current_top_module {
285        current_top_module = submodule_id.parent_module(db.upcast());
286    }
287    let current_crate_id = match current_top_module {
288        ModuleId::CrateRoot(crate_id) => crate_id,
289        ModuleId::Submodule(_) => unreachable!("current module is not a top-level module"),
290    };
291    let edition = db.crate_config(current_crate_id)?.settings.edition;
292    let prelude_submodule_name = edition.prelude_submodule_name();
293    let core_prelude_submodule = core_submodule(db, "prelude");
294    let prelude_submodule = get_submodule(db, core_prelude_submodule, prelude_submodule_name)?;
295    let prelude_submodule_file_id = ModuleFileId(prelude_submodule, FileIndex(0));
296
297    let mut module_visible_importables = Vec::new();
298    // Collect importables from the prelude.
299    module_visible_importables.extend_from_slice(
300        &db.visible_importables_in_module(prelude_submodule, prelude_submodule_file_id, false)[..],
301    );
302    // Collect importables from all visible crates, including the current crate.
303    let settings = db.crate_config(current_crate_id).map(|c| c.settings).unwrap_or_default();
304    for crate_id in chain!(
305        [current_crate_id],
306        (!settings.dependencies.contains_key(CORELIB_CRATE_NAME)).then(|| corelib::core_crate(db)),
307        settings.dependencies.iter().map(|(name, setting)| {
308            CrateLongId::Real {
309                name: name.clone().into(),
310                discriminator: setting.discriminator.clone(),
311            }
312            .intern(db)
313        })
314    ) {
315        module_visible_importables
316            .extend_from_slice(&db.visible_importables_in_crate(crate_id, module_file_id)[..]);
317    }
318
319    // Collect importables visible in the current module.
320    module_visible_importables
321        .extend_from_slice(&db.visible_importables_in_module(module_id, module_file_id, true)[..]);
322
323    // Deduplicate importables, preferring shorter paths.
324    // This is the reason for searching in the crates before the current module- to prioritize
325    // shorter, canonical paths prefixed with `crate::` over paths using `super::` or local
326    // imports.
327    let mut result: OrderedHashMap<ImportableId, String> = OrderedHashMap::default();
328    for (trait_id, path) in module_visible_importables {
329        match result.entry(trait_id) {
330            Entry::Occupied(existing_path) => {
331                if path.split("::").count() < existing_path.get().split("::").count() {
332                    *existing_path.into_mut() = path;
333                }
334            }
335            Entry::Vacant(entry) => {
336                entry.insert(path);
337            }
338        }
339    }
340    Some(result.into())
341}
342
343/// Query implementation of [crate::db::SemanticGroup::visible_traits_from_module].
344pub fn visible_traits_from_module(
345    db: &dyn SemanticGroup,
346    module_file_id: ModuleFileId,
347) -> Option<Arc<OrderedHashMap<TraitId, String>>> {
348    let importables = db.visible_importables_from_module(module_file_id)?;
349
350    let traits = importables
351        .iter()
352        .filter_map(|(item, path)| {
353            if let ImportableId::Trait(trait_id) = item {
354                Some((*trait_id, path.clone()))
355            } else {
356                None
357            }
358        })
359        .collect::<OrderedHashMap<_, _>>()
360        .into();
361
362    Some(traits)
363}