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#[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 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#[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 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 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
199pub 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#[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#[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#[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
272fn 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 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 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 let Ok(resolved_item) = db.use_resolved_item(use_id) else {
328 continue;
329 };
330 match resolved_item {
331 ResolvedGenericItem::Trait(trait_id) => {
333 module_traits
334 .insert(trait_id, LookupItemId::ModuleItem(ModuleItemId::Use(use_id)));
335 }
336 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
358pub trait ModuleSemantic<'db>: Database {
360 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 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 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 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 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 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 {}