cairo_lang_semantic/items/
module.rs

1use cairo_lang_defs::db::DefsGroup;
2use cairo_lang_defs::ids::{
3    GlobalUseId, LanguageElementId, LookupItemId, ModuleId, ModuleItemId, NamedLanguageElementId,
4    TraitId, UseId,
5};
6use cairo_lang_diagnostics::{Diagnostics, DiagnosticsBuilder, Maybe, MaybeAsRef};
7use cairo_lang_filesystem::db::CrateSettings;
8use cairo_lang_filesystem::ids::{SmolStrId, Tracked};
9use cairo_lang_syntax::attribute::structured::{Attribute, AttributeListStructurize};
10use cairo_lang_syntax::node::ast;
11use cairo_lang_syntax::node::helpers::UsePathEx;
12use cairo_lang_utils::ordered_hash_map::OrderedHashMap;
13use cairo_lang_utils::ordered_hash_set::OrderedHashSet;
14use itertools::chain;
15use salsa::Database;
16
17use super::feature_kind::FeatureKind;
18use super::us::SemanticUseEx;
19use super::visibility::{Visibility, peek_visible_in};
20use crate::SemanticDiagnostic;
21use crate::corelib::{core_submodule, get_submodule};
22use crate::db::{SemanticGroup, get_resolver_data_options};
23use crate::diagnostic::{SemanticDiagnosticKind, SemanticDiagnosticsBuilder};
24use crate::items::feature_kind::HasFeatureKind;
25use crate::items::imp::ImplSemantic;
26use crate::items::impl_alias::ImplAliasSemantic;
27use crate::items::macro_call::module_macro_modules;
28use crate::items::trt::TraitSemantic;
29use crate::items::us::{ImportInfo, UseSemantic};
30use crate::resolve::ResolvedGenericItem;
31
32/// Information per item in a module.
33#[derive(Clone, Debug, PartialEq, Eq, salsa::Update)]
34pub struct ModuleItemInfo<'db> {
35    pub item_id: ModuleItemId<'db>,
36    pub visibility: Visibility,
37    pub feature_kind: FeatureKind<'db>,
38}
39
40#[derive(Clone, Debug, PartialEq, Eq, salsa::Update)]
41pub struct ModuleSemanticData<'db> {
42    /// The items in the module without duplicates.
43    pub items: OrderedHashMap<SmolStrId<'db>, ModuleItemInfo<'db>>,
44    pub global_uses: OrderedHashMap<GlobalUseId<'db>, Visibility>,
45    pub diagnostics: Diagnostics<'db, SemanticDiagnostic<'db>>,
46}
47
48/// Query implementation of [ModuleSemantic::priv_module_semantic_data].
49#[salsa::tracked(returns(ref))]
50fn priv_module_semantic_data<'db>(
51    db: &'db dyn Database,
52    _tracked: Tracked,
53    module_id: ModuleId<'db>,
54) -> Maybe<ModuleSemanticData<'db>> {
55    if let Some(data) = db.cached_crate_semantic_data(module_id.owning_crate(db)) {
56        if let Some(module_data) = data.modules_semantic_data.get(&module_id) {
57            return Ok(module_data.clone());
58        } else {
59            panic!("module not found in cached modules_data {:?}", module_id.name(db));
60        }
61    };
62    // We use the builder here since the items can come from different file_ids.
63    let mut diagnostics = DiagnosticsBuilder::default();
64    let mut items = OrderedHashMap::default();
65    let module_data = module_id.module_data(db)?;
66    for item_id in module_data.items(db).iter().copied() {
67        let (name, attributes, visibility) = match &item_id {
68            ModuleItemId::Constant(item_id) => {
69                let item = &module_data.constants(db)[item_id];
70                (item_id.name(db), item.attributes(db), item.visibility(db))
71            }
72            ModuleItemId::Submodule(item_id) => {
73                let item = &module_data.submodules(db)[item_id];
74                (item_id.name(db), item.attributes(db), item.visibility(db))
75            }
76            ModuleItemId::Use(item_id) => {
77                let use_ast = &module_data.uses(db)[item_id];
78                let item = ast::UsePath::Leaf(use_ast.clone()).get_item(db);
79                (item_id.name(db), item.attributes(db), item.visibility(db))
80            }
81            ModuleItemId::FreeFunction(item_id) => {
82                let item = &module_data.free_functions(db)[item_id];
83                (item_id.name(db), item.attributes(db), item.visibility(db))
84            }
85            ModuleItemId::Struct(item_id) => {
86                let item = &module_data.structs(db)[item_id];
87                (item_id.name(db), item.attributes(db), item.visibility(db))
88            }
89            ModuleItemId::Enum(item_id) => {
90                let item = &module_data.enums(db)[item_id];
91                (item_id.name(db), item.attributes(db), item.visibility(db))
92            }
93            ModuleItemId::TypeAlias(item_id) => {
94                let item = &module_data.type_aliases(db)[item_id];
95                (item_id.name(db), item.attributes(db), item.visibility(db))
96            }
97            ModuleItemId::ImplAlias(item_id) => {
98                let item = &module_data.impl_aliases(db)[item_id];
99                (item_id.name(db), item.attributes(db), item.visibility(db))
100            }
101            ModuleItemId::Trait(item_id) => {
102                let item = &module_data.traits(db)[item_id];
103                (item_id.name(db), item.attributes(db), item.visibility(db))
104            }
105            ModuleItemId::Impl(item_id) => {
106                let item = &module_data.impls(db)[item_id];
107                (item_id.name(db), item.attributes(db), item.visibility(db))
108            }
109            ModuleItemId::ExternType(item_id) => {
110                let item = &module_data.extern_types(db)[item_id];
111                (item_id.name(db), item.attributes(db), item.visibility(db))
112            }
113            ModuleItemId::ExternFunction(item_id) => {
114                let item = &module_data.extern_functions(db)[item_id];
115                (item_id.name(db), item.attributes(db), item.visibility(db))
116            }
117            ModuleItemId::MacroDeclaration(item_id) => {
118                let item = &module_data.macro_declarations(db)[item_id];
119                (item_id.name(db), item.attributes(db), item.visibility(db))
120            }
121        };
122        let visibility = Visibility::from_ast(db, &mut diagnostics, &visibility);
123        let feature_kind = FeatureKind::from_ast(db, &mut diagnostics, &attributes);
124        if items.insert(name, ModuleItemInfo { item_id, visibility, feature_kind }).is_some() {
125            // `item` is extracted from `module_items` and thus `module_item_name_stable_ptr` is
126            // guaranteed to succeed.
127            diagnostics.report(
128                db.module_item_name_stable_ptr(module_id, item_id).unwrap(),
129                SemanticDiagnosticKind::NameDefinedMultipleTimes(name),
130            );
131        }
132    }
133
134    let global_uses = module_data
135        .global_uses(db)
136        .iter()
137        .map(|(global_use_id, use_path_star)| {
138            let item = ast::UsePath::Star(use_path_star.clone()).get_item(db);
139            let visibility = item.visibility(db);
140            (*global_use_id, Visibility::from_ast(db, &mut diagnostics, &visibility))
141        })
142        .collect();
143    Ok(ModuleSemanticData { items, global_uses, diagnostics: diagnostics.build() })
144}
145
146pub fn module_item_by_name<'db>(
147    db: &'db dyn Database,
148    module_id: ModuleId<'db>,
149    name: SmolStrId<'db>,
150) -> Maybe<Option<ModuleItemId<'db>>> {
151    let module_data = db.priv_module_semantic_data(module_id)?;
152    Ok(module_data.items.get(&name).map(|info| info.item_id))
153}
154
155fn module_item_by_name_tracked<'db>(
156    db: &'db dyn Database,
157    module_id: ModuleId<'db>,
158    name: SmolStrId<'db>,
159) -> Maybe<Option<ModuleItemId<'db>>> {
160    module_item_by_name_helper(db, (), module_id, name)
161}
162
163#[salsa::tracked]
164fn module_item_by_name_helper<'db>(
165    db: &'db dyn Database,
166    _tracked: Tracked,
167    module_id: ModuleId<'db>,
168    name: SmolStrId<'db>,
169) -> Maybe<Option<ModuleItemId<'db>>> {
170    module_item_by_name(db, module_id, name)
171}
172
173pub fn module_item_info_by_name<'db>(
174    db: &'db dyn Database,
175    module_id: ModuleId<'db>,
176
177    name: SmolStrId<'db>,
178) -> Maybe<Option<ModuleItemInfo<'db>>> {
179    let module_data = db.priv_module_semantic_data(module_id)?;
180    Ok(module_data.items.get(&name).cloned())
181}
182
183fn module_item_info_by_name_tracked<'db>(
184    db: &'db dyn Database,
185    module_id: ModuleId<'db>,
186    name: SmolStrId<'db>,
187) -> Maybe<Option<ModuleItemInfo<'db>>> {
188    module_item_info_by_name_helper(db, (), module_id, name)
189}
190
191#[salsa::tracked]
192fn module_item_info_by_name_helper<'db>(
193    db: &'db dyn Database,
194    _tracked: Tracked,
195    module_id: ModuleId<'db>,
196    name: SmolStrId<'db>,
197) -> Maybe<Option<ModuleItemInfo<'db>>> {
198    module_item_info_by_name(db, module_id, name)
199}
200
201/// Gets the imported global uses of a module, and their visibility.
202pub fn get_module_global_uses<'db>(
203    db: &'db dyn Database,
204    module_id: ModuleId<'db>,
205) -> Maybe<OrderedHashMap<GlobalUseId<'db>, Visibility>> {
206    let module_data = db.priv_module_semantic_data(module_id)?;
207    Ok(module_data.global_uses.clone())
208}
209
210/// Query implementation of [ModuleSemantic::module_all_used_uses].
211#[salsa::tracked(returns(ref))]
212pub fn module_all_used_uses<'db>(
213    db: &'db dyn Database,
214    _tracked: Tracked,
215    module_id: ModuleId<'db>,
216) -> Maybe<OrderedHashSet<UseId<'db>>> {
217    let mut all_used_uses = OrderedHashSet::default();
218    let module_items = module_id.module_data(db)?.items(db);
219    for item in module_items.iter() {
220        if let Some(items) = match *item {
221            ModuleItemId::Submodule(submodule_id) => {
222                Some(db.module_all_used_uses(ModuleId::Submodule(submodule_id))?)
223            }
224            ModuleItemId::Trait(trait_id) => Some(db.trait_all_used_uses(trait_id)?),
225            ModuleItemId::Impl(impl_id) => Some(db.impl_all_used_uses(impl_id)?),
226            _ => None,
227        } {
228            all_used_uses.extend(items.iter().cloned());
229        } else {
230            for resolver_data in get_resolver_data_options(LookupItemId::ModuleItem(*item), db) {
231                all_used_uses.extend(resolver_data.used_uses.iter().cloned());
232            }
233        }
234    }
235    Ok(all_used_uses)
236}
237
238/// Query implementation of [ModuleSemantic::module_attributes].
239#[salsa::tracked(returns(ref))]
240pub fn module_attributes<'db>(
241    db: &'db dyn Database,
242    _tracked: Tracked,
243    module_id: ModuleId<'db>,
244) -> Maybe<Vec<Attribute<'db>>> {
245    Ok(match &module_id {
246        ModuleId::CrateRoot(_) | ModuleId::MacroCall { .. } => vec![],
247        ModuleId::Submodule(submodule_id) => {
248            let module_ast = &submodule_id.module_data(db)?.submodules(db)[submodule_id];
249
250            module_ast.attributes(db).structurize(db)
251        }
252    })
253}
254
255/// Finds all the trait ids usable in the current context, using `global use` imports.
256/// Query implementation of [ModuleSemantic::module_usable_trait_ids].
257#[salsa::tracked(returns(ref))]
258pub fn module_usable_trait_ids<'db>(
259    db: &'db dyn Database,
260    _tracked: Tracked,
261    module_id: ModuleId<'db>,
262) -> OrderedHashMap<TraitId<'db>, LookupItemId<'db>> {
263    let mut module_traits = OrderedHashMap::<TraitId<'db>, LookupItemId<'db>>::default();
264    for (containing_module, info) in db.module_imported_modules((), module_id).iter() {
265        for defined_module in
266            chain!([containing_module], module_macro_modules(db, false, *containing_module))
267        {
268            extend_specific_module_usable_trait_ids(db, info, *defined_module, &mut module_traits);
269        }
270    }
271    module_traits
272}
273
274/// Extends the `module_traits` with all the trait ids usable in the current context, not using
275/// `global use` imports.
276fn extend_specific_module_usable_trait_ids<'db>(
277    db: &'db dyn Database,
278    info: &ImportInfo<'db>,
279    containing_module: ModuleId<'db>,
280    module_traits: &mut OrderedHashMap<TraitId<'db>, LookupItemId<'db>>,
281) {
282    let Ok(data) = db.priv_module_semantic_data(containing_module) else {
283        return;
284    };
285    for item in data.items.values() {
286        if !matches!(
287            item.item_id,
288            ModuleItemId::Trait(_)
289                | ModuleItemId::Impl(_)
290                | ModuleItemId::ImplAlias(_)
291                | ModuleItemId::Use(_)
292        ) {
293            continue;
294        }
295        if !info.user_modules.iter().any(|user_module_id| {
296            peek_visible_in(db, item.visibility, containing_module, *user_module_id)
297        }) {
298            continue;
299        }
300        match item.item_id {
301            ModuleItemId::Trait(trait_id) => {
302                module_traits
303                    .entry(trait_id)
304                    .or_insert(LookupItemId::ModuleItem(ModuleItemId::Trait(trait_id)));
305            }
306            ModuleItemId::Impl(impl_def_id) => {
307                // Add traits from impls in the module.
308                let Ok(trait_id) = db.impl_def_trait(impl_def_id) else {
309                    continue;
310                };
311                module_traits
312                    .entry(trait_id)
313                    .or_insert(LookupItemId::ModuleItem(ModuleItemId::Impl(impl_def_id)));
314            }
315            ModuleItemId::ImplAlias(impl_alias_id) => {
316                // Add traits from impl aliases in the module.
317                let Ok(impl_id) = db.impl_alias_impl_def(impl_alias_id) else {
318                    continue;
319                };
320                let Ok(trait_id) = db.impl_def_trait(impl_id) else {
321                    continue;
322                };
323                module_traits
324                    .entry(trait_id)
325                    .or_insert(LookupItemId::ModuleItem(ModuleItemId::ImplAlias(impl_alias_id)));
326            }
327            ModuleItemId::Use(use_id) => {
328                // Add traits from uses in the module.
329                let Ok(resolved_item) = db.use_resolved_item(use_id) else {
330                    continue;
331                };
332                match resolved_item {
333                    // use of a trait.
334                    ResolvedGenericItem::Trait(trait_id) => {
335                        module_traits
336                            .insert(trait_id, LookupItemId::ModuleItem(ModuleItemId::Use(use_id)));
337                    }
338                    // use of an impl from which we get the trait.
339                    ResolvedGenericItem::Impl(impl_def_id) => {
340                        if let Ok(trait_id) = db.impl_def_trait(impl_def_id) {
341                            module_traits
342                                .entry(trait_id)
343                                .or_insert(LookupItemId::ModuleItem(ModuleItemId::Use(use_id)));
344                        };
345                    }
346                    _ => {}
347                }
348            }
349            _ => {}
350        }
351    }
352}
353
354impl<'db> HasFeatureKind<'db> for ModuleItemInfo<'db> {
355    fn feature_kind(&self) -> &FeatureKind<'db> {
356        &self.feature_kind
357    }
358}
359
360/// Trait for module-related semantic queries.
361pub trait ModuleSemantic<'db>: Database {
362    /// Private query to compute data about the module.
363    fn priv_module_semantic_data(
364        &'db self,
365        module_id: ModuleId<'db>,
366    ) -> Maybe<&'db ModuleSemanticData<'db>> {
367        priv_module_semantic_data(self.as_dyn_database(), (), module_id).maybe_as_ref()
368    }
369    /// Returns [Maybe::Err] if the module was not properly resolved.
370    /// Returns [Maybe::Ok(None)] if the item does not exist.
371    fn module_item_by_name(
372        &'db self,
373        module_id: ModuleId<'db>,
374        name: SmolStrId<'db>,
375    ) -> Maybe<Option<ModuleItemId<'db>>> {
376        module_item_by_name_tracked(self.as_dyn_database(), module_id, name)
377    }
378    /// Returns [Maybe::Err] if the module was not properly resolved.
379    /// Returns [Maybe::Ok(None)] if the item does not exist.
380    fn module_item_info_by_name(
381        &'db self,
382        module_id: ModuleId<'db>,
383
384        name: SmolStrId<'db>,
385    ) -> Maybe<Option<ModuleItemInfo<'db>>> {
386        module_item_info_by_name_tracked(self.as_dyn_database(), module_id, name)
387    }
388    /// Returns all the items used within the module.
389    fn module_all_used_uses(
390        &'db self,
391        module_id: ModuleId<'db>,
392    ) -> Maybe<&'db OrderedHashSet<UseId<'db>>> {
393        module_all_used_uses(self.as_dyn_database(), (), module_id).maybe_as_ref()
394    }
395    /// Returns the attributes of a module.
396    fn module_attributes(&'db self, module_id: ModuleId<'db>) -> Maybe<&'db [Attribute<'db>]> {
397        Ok(module_attributes(self.as_dyn_database(), (), module_id).maybe_as_ref()?)
398    }
399    /// Finds all the trait ids usable in the module.
400    fn module_usable_trait_ids(
401        &'db self,
402        module_id: ModuleId<'db>,
403    ) -> &'db OrderedHashMap<TraitId<'db>, LookupItemId<'db>> {
404        module_usable_trait_ids(self.as_dyn_database(), (), module_id)
405    }
406    /// Finds the prelude submodule of the core module according to the crate settings.
407    fn get_prelude_submodule(&'db self, settings: &CrateSettings) -> Option<ModuleId<'db>> {
408        let db = self.as_dyn_database();
409        let prelude_submodule_name = settings.edition.prelude_submodule_name(db);
410        let core_prelude_submodule = core_submodule(db, SmolStrId::from(db, "prelude"));
411        get_submodule(db, core_prelude_submodule, prelude_submodule_name)
412    }
413}
414
415impl<'db, T: Database + ?Sized> ModuleSemantic<'db> for T {}