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