cairo_lang_semantic/
lsp_helpers.rs

1use std::sync::Arc;
2
3use cairo_lang_defs::ids::{
4    FileIndex, LanguageElementId, ModuleFileId, ModuleId, NamedLanguageElementId, TraitFunctionId,
5    TraitId,
6};
7use cairo_lang_filesystem::ids::CrateId;
8use cairo_lang_utils::ordered_hash_map::{Entry, OrderedHashMap};
9use cairo_lang_utils::unordered_hash_set::UnorderedHashSet;
10use smol_str::SmolStr;
11
12use crate::corelib::{core_submodule, get_submodule};
13use crate::db::SemanticGroup;
14use crate::expr::inference::InferenceId;
15use crate::items::us::SemanticUseEx;
16use crate::items::visibility::peek_visible_in;
17use crate::resolve::{ResolvedGenericItem, Resolver};
18use crate::types::TypeHead;
19
20/// A filter for types.
21#[derive(Clone, Debug, Hash, PartialEq, Eq)]
22pub enum TypeFilter {
23    /// No filter is applied.
24    NoFilter,
25    /// Only methods with the given type head are returned.
26    TypeHead(TypeHead),
27}
28
29/// Query implementation of [crate::db::SemanticGroup::methods_in_module].
30pub fn methods_in_module(
31    db: &dyn SemanticGroup,
32    module_id: ModuleId,
33    type_filter: TypeFilter,
34) -> Arc<[TraitFunctionId]> {
35    let mut result = Vec::new();
36    let Ok(module_traits_ids) = db.module_traits_ids(module_id) else {
37        return result.into();
38    };
39    for trait_id in module_traits_ids.iter().copied() {
40        for (_, trait_function) in db.trait_functions(trait_id).unwrap_or_default() {
41            let Ok(signature) = db.trait_function_signature(trait_function) else {
42                continue;
43            };
44            let Some(first_param) = signature.params.first() else {
45                continue;
46            };
47            if first_param.name != "self" {
48                continue;
49            }
50            if let TypeFilter::TypeHead(type_head) = &type_filter {
51                if let Some(head) = first_param.ty.head(db) {
52                    if !fit_for_method(&head, type_head) {
53                        continue;
54                    }
55                }
56            }
57
58            result.push(trait_function)
59        }
60    }
61    result.into()
62}
63
64/// Checks if a type head can fit for a method.
65fn fit_for_method(head: &TypeHead, type_head: &TypeHead) -> bool {
66    if head == type_head {
67        return true;
68    }
69    if let TypeHead::Snapshot(snapshot_head) = head {
70        return snapshot_head.as_ref() == type_head;
71    }
72    false
73}
74
75/// Query implementation of [crate::db::SemanticGroup::methods_in_crate].
76pub fn methods_in_crate(
77    db: &dyn SemanticGroup,
78    crate_id: CrateId,
79    type_filter: TypeFilter,
80) -> Arc<[TraitFunctionId]> {
81    let mut result = Vec::new();
82    for module_id in db.crate_modules(crate_id).iter() {
83        result.extend_from_slice(&db.methods_in_module(*module_id, type_filter.clone())[..])
84    }
85    result.into()
86}
87
88/// Query implementation of [crate::db::SemanticGroup::visible_traits_in_module].
89pub fn visible_traits_in_module(
90    db: &dyn SemanticGroup,
91    module_id: ModuleId,
92    user_module_file_id: ModuleFileId,
93    include_parent: bool,
94) -> Arc<[(TraitId, String)]> {
95    let mut visited_modules = UnorderedHashSet::default();
96    visible_traits_in_module_ex(
97        db,
98        module_id,
99        user_module_file_id,
100        include_parent,
101        &mut visited_modules,
102    )
103    .unwrap_or_else(|| Vec::new().into())
104}
105
106/// Returns the visible traits in a module, including the traits in the parent module if needed.
107/// The visibility is relative to the module `user_module_id`.
108fn visible_traits_in_module_ex(
109    db: &dyn SemanticGroup,
110    module_id: ModuleId,
111    user_module_file_id: ModuleFileId,
112    include_parent: bool,
113    visited_modules: &mut UnorderedHashSet<ModuleId>,
114) -> Option<Arc<[(TraitId, String)]>> {
115    let mut result = Vec::new();
116    if visited_modules.contains(&module_id) {
117        return Some(result.into());
118    }
119
120    let resolver = Resolver::new(db, user_module_file_id, InferenceId::NoContext);
121    let ignore_visibility = resolver.ignore_visibility_checks(module_id);
122    // Check if an item in the current module is visible from the user module.
123    let is_visible = |item_name: SmolStr| {
124        if ignore_visibility {
125            Some(true)
126        } else {
127            let item_info = db.module_item_info_by_name(module_id, item_name).ok()??;
128            Some(peek_visible_in(
129                db.upcast(),
130                item_info.visibility,
131                module_id,
132                user_module_file_id.0,
133            ))
134        }
135    };
136    visited_modules.insert(module_id);
137    let mut modules_to_visit = vec![];
138    // Add traits and traverse modules imported into the current module.
139    for use_id in db.module_uses_ids(module_id).ok()?.iter().copied() {
140        if !is_visible(use_id.name(db.upcast()))? {
141            continue;
142        }
143        let resolved_item = db.use_resolved_item(use_id).ok()?;
144        match resolved_item {
145            ResolvedGenericItem::Module(inner_module_id) => {
146                modules_to_visit.push(inner_module_id);
147            }
148            ResolvedGenericItem::Trait(trait_id) => {
149                result.push((trait_id, trait_id.name(db.upcast()).to_string()));
150            }
151            _ => continue,
152        }
153    }
154    // Traverse the submodules of the current module.
155    for submodule_id in db.module_submodules_ids(module_id).ok()?.iter().copied() {
156        if !is_visible(submodule_id.name(db.upcast()))? {
157            continue;
158        }
159        modules_to_visit.push(ModuleId::Submodule(submodule_id));
160    }
161    // Add the traits of the current module.
162    for trait_id in db.module_traits_ids(module_id).ok()?.iter().copied() {
163        if !is_visible(trait_id.name(db.upcast()))? {
164            continue;
165        }
166        result.push((trait_id, trait_id.name(db.upcast()).to_string()));
167    }
168
169    for submodule in modules_to_visit {
170        for (trait_id, path) in visible_traits_in_module_ex(
171            db,
172            submodule,
173            user_module_file_id,
174            include_parent,
175            visited_modules,
176        )?
177        .iter()
178        {
179            result.push((*trait_id, format!("{}::{}", submodule.name(db.upcast()), path)));
180        }
181    }
182    // Traverse the parent module if needed.
183    if include_parent {
184        match module_id {
185            ModuleId::CrateRoot(_) => {}
186            ModuleId::Submodule(submodule_id) => {
187                let parent_module_id = submodule_id.parent_module(db.upcast());
188                for (trait_id, path) in visible_traits_in_module_ex(
189                    db,
190                    parent_module_id,
191                    user_module_file_id,
192                    include_parent,
193                    visited_modules,
194                )?
195                .iter()
196                {
197                    result.push((*trait_id, format!("super::{}", path)));
198                }
199            }
200        }
201    }
202    Some(result.into())
203}
204
205/// Query implementation of [crate::db::SemanticGroup::visible_traits_in_crate].
206pub fn visible_traits_in_crate(
207    db: &dyn SemanticGroup,
208    crate_id: CrateId,
209    user_module_file_id: ModuleFileId,
210) -> Arc<[(TraitId, String)]> {
211    let crate_name = crate_id.name(db.upcast());
212    let crate_as_module = ModuleId::CrateRoot(crate_id);
213    db.visible_traits_in_module(crate_as_module, user_module_file_id, false)
214        .iter()
215        .cloned()
216        .map(|(trait_id, path)| (trait_id, format!("{crate_name}::{path}",)))
217        .collect::<Vec<_>>()
218        .into()
219}
220
221/// Query implementation of [crate::db::SemanticGroup::visible_traits_from_module].
222pub fn visible_traits_from_module(
223    db: &dyn SemanticGroup,
224    module_file_id: ModuleFileId,
225) -> Option<Arc<OrderedHashMap<TraitId, String>>> {
226    let module_id = module_file_id.0;
227    let mut current_top_module = module_id;
228    while let ModuleId::Submodule(submodule_id) = current_top_module {
229        current_top_module = submodule_id.parent_module(db.upcast());
230    }
231    let current_crate_id = match current_top_module {
232        ModuleId::CrateRoot(crate_id) => crate_id,
233        ModuleId::Submodule(_) => unreachable!("current module is not a top-level module"),
234    };
235    let edition = db.crate_config(current_crate_id)?.settings.edition;
236    let prelude_submodule_name = edition.prelude_submodule_name();
237    let core_prelude_submodule = core_submodule(db, "prelude");
238    let prelude_submodule = get_submodule(db, core_prelude_submodule, prelude_submodule_name)?;
239    let prelude_submodule_file_id = ModuleFileId(prelude_submodule, FileIndex(0));
240
241    let mut module_visible_traits = Vec::new();
242    module_visible_traits.extend_from_slice(
243        &db.visible_traits_in_module(prelude_submodule, prelude_submodule_file_id, false)[..],
244    );
245    module_visible_traits
246        .extend_from_slice(&db.visible_traits_in_module(module_id, module_file_id, true)[..]);
247    for crate_id in db.crates() {
248        if crate_id == current_crate_id {
249            continue;
250        }
251        module_visible_traits
252            .extend_from_slice(&db.visible_traits_in_crate(crate_id, module_file_id)[..]);
253    }
254    let mut result: OrderedHashMap<TraitId, String> = OrderedHashMap::default();
255    for (trait_id, path) in module_visible_traits {
256        match result.entry(trait_id) {
257            Entry::Occupied(existing_path) => {
258                if path.split("::").count() < existing_path.get().split("::").count() {
259                    *existing_path.into_mut() = path;
260                }
261            }
262            Entry::Vacant(entry) => {
263                entry.insert(path);
264            }
265        }
266    }
267    Some(result.into())
268}