Skip to main content

cairo_lang_semantic/items/
us.rs

1use std::sync::Arc;
2
3use cairo_lang_defs::db::DefsGroup;
4use cairo_lang_defs::ids::{
5    GlobalUseId, LanguageElementId, LookupItemId, ModuleId, ModuleItemId, UseId,
6};
7use cairo_lang_diagnostics::{Diagnostics, Maybe};
8use cairo_lang_filesystem::ids::Tracked;
9use cairo_lang_proc_macros::DebugWithDb;
10use cairo_lang_syntax::node::helpers::UsePathEx;
11use cairo_lang_syntax::node::kind::SyntaxKind;
12use cairo_lang_syntax::node::{TypedSyntaxNode, ast};
13use cairo_lang_utils::ordered_hash_map::{Entry, OrderedHashMap};
14use cairo_lang_utils::unordered_hash_set::UnorderedHashSet;
15use itertools::{Itertools, chain};
16use salsa::Database;
17
18use super::module::get_module_global_uses;
19use super::visibility::peek_visible_in;
20use crate::SemanticDiagnostic;
21use crate::diagnostic::SemanticDiagnosticKind::*;
22use crate::diagnostic::{
23    ElementKind, NotFoundItemType, SemanticDiagnostics, SemanticDiagnosticsBuilder,
24};
25use crate::expr::inference::InferenceId;
26use crate::items::macro_call::module_macro_modules;
27use crate::resolve::{ResolutionContext, ResolvedGenericItem, Resolver, ResolverData};
28
29#[derive(Clone, Debug, PartialEq, Eq, DebugWithDb, salsa::Update)]
30#[debug_db(dyn Database)]
31pub struct UseData<'db> {
32    diagnostics: Diagnostics<'db, SemanticDiagnostic<'db>>,
33    resolved_item: Maybe<ResolvedGenericItem<'db>>,
34    resolver_data: Arc<ResolverData<'db>>,
35}
36
37/// Represents path segments in a use statement, with support for a leading dollar token ($).
38pub struct UseAsPathSegments<'db> {
39    pub segments: Vec<ast::PathSegment<'db>>,
40    pub is_placeholder: Option<ast::TerminalDollar<'db>>,
41}
42
43/// Indicates whether we found a UsePathSingle node, a leading dollar token ($), or hit ItemUse
44/// without a dollar when traversing the AST.
45pub enum UsePathOrDollar<'db> {
46    UsePathSingle(ast::UsePathSingle<'db>),
47    Dollar(ast::TerminalDollar<'db>),
48    None,
49}
50
51/// Implementation of [UseSemantic::priv_use_semantic_data].
52fn priv_use_semantic_data<'db>(
53    db: &'db dyn Database,
54    use_id: UseId<'db>,
55) -> Maybe<Arc<UseData<'db>>> {
56    let module_id = use_id.parent_module(db);
57    let mut diagnostics = SemanticDiagnostics::new(module_id);
58    let module_item_id = ModuleItemId::Use(use_id);
59    let inference_id = InferenceId::LookupItemDeclaration(LookupItemId::ModuleItem(module_item_id));
60    let mut resolver = Resolver::new(db, module_id, inference_id);
61    // TODO(spapini): when code changes in a file, all the AST items change (as they contain a path
62    // to the green root that changes. Once ASTs are rooted on items, use a selector that picks only
63    // the item instead of all the module data.
64    let use_ast = ast::UsePath::Leaf(db.module_use_by_id(use_id)?);
65    let item = use_ast.get_item(db);
66    resolver.set_feature_config(&use_id, &item, &mut diagnostics);
67    let resolved_item = resolver.resolve_use_path(
68        &mut diagnostics,
69        use_ast,
70        ResolutionContext::ModuleItem(module_item_id),
71    );
72    let resolver_data: Arc<ResolverData<'_>> = Arc::new(resolver.data);
73
74    Ok(Arc::new(UseData { diagnostics: diagnostics.build(), resolved_item, resolver_data }))
75}
76
77/// Query implementation of [UseSemantic::priv_use_semantic_data].
78#[salsa::tracked(cycle_result=priv_use_semantic_data_cycle)]
79fn priv_use_semantic_data_tracked<'db>(
80    db: &'db dyn Database,
81    use_id: UseId<'db>,
82) -> Maybe<Arc<UseData<'db>>> {
83    priv_use_semantic_data(db, use_id)
84}
85
86/// Returns the segments that are the parts of the use path.
87///
88/// The segments are returned in the order they appear in the use path.
89/// Only `UsePathLeaf` and `UsePathSingle` are supported.
90///
91/// For example:
92/// Given the `c` of `use a::b::{c, d};` will return `[a, b, c]`.
93/// Given the `b` of `use a::b::{c, d};` will return `[a, b]`.
94pub fn get_use_path_segments<'db>(
95    db: &'db dyn Database,
96    use_path: ast::UsePath<'db>,
97) -> Maybe<UseAsPathSegments<'db>> {
98    let mut rev_segments = vec![];
99    match &use_path {
100        ast::UsePath::Leaf(use_ast) => rev_segments.push(use_ast.ident(db)),
101        ast::UsePath::Single(use_ast) => rev_segments.push(use_ast.ident(db)),
102        ast::UsePath::Star(_) => {}
103        ast::UsePath::Multi(_) => {
104            panic!("Only `UsePathLeaf` and `UsePathSingle` are supported.")
105        }
106    }
107    let mut current_path = use_path;
108    let mut dollar = None;
109    loop {
110        match get_parent_single_use_path(db, &current_path)? {
111            UsePathOrDollar::UsePathSingle(parent_use_path) => {
112                let ident = parent_use_path.ident(db);
113                rev_segments.push(ident);
114                current_path = ast::UsePath::Single(parent_use_path);
115            }
116            UsePathOrDollar::Dollar(d) => {
117                dollar = Some(d);
118                break;
119            }
120            UsePathOrDollar::None => break,
121        }
122    }
123    Ok(UseAsPathSegments {
124        segments: rev_segments.into_iter().rev().collect(),
125        is_placeholder: dollar,
126    })
127}
128
129/// Returns the parent `UsePathSingle` of a use path if it exists.
130fn get_parent_single_use_path<'db>(
131    db: &'db dyn Database,
132    use_path: &ast::UsePath<'db>,
133) -> Maybe<UsePathOrDollar<'db>> {
134    let node = use_path.as_syntax_node();
135    let parent = node.parent(db).expect("`UsePath` is not under an `ItemUse`.");
136    match parent.kind(db) {
137        SyntaxKind::UsePathSingle => {
138            Ok(UsePathOrDollar::UsePathSingle(ast::UsePathSingle::from_syntax_node(db, parent)))
139        }
140        SyntaxKind::ItemUse => {
141            let typed_use_node = ast::ItemUse::from_syntax_node(db, parent);
142            Ok(match typed_use_node.dollar(db) {
143                ast::OptionTerminalDollar::TerminalDollar(dollar) => {
144                    UsePathOrDollar::Dollar(dollar)
145                }
146                ast::OptionTerminalDollar::Empty(_) => UsePathOrDollar::None,
147            })
148        }
149        SyntaxKind::UsePathList | SyntaxKind::UsePathMulti => {
150            let mut current = parent;
151            while let Some(parent) = current.parent(db) {
152                match parent.kind(db) {
153                    SyntaxKind::UsePathSingle => {
154                        return Ok(UsePathOrDollar::UsePathSingle(
155                            ast::UsePathSingle::from_syntax_node(db, parent),
156                        ));
157                    }
158                    SyntaxKind::ItemUse => {
159                        let item_use = ast::ItemUse::from_syntax_node(db, parent);
160                        if let ast::OptionTerminalDollar::TerminalDollar(d) = item_use.dollar(db) {
161                            return Ok(UsePathOrDollar::Dollar(d));
162                        }
163                        return Ok(UsePathOrDollar::None);
164                    }
165                    SyntaxKind::UsePathList | SyntaxKind::UsePathMulti => {
166                        current = parent;
167                        continue;
168                    }
169                    _ => return Ok(UsePathOrDollar::None),
170                }
171            }
172            Ok(UsePathOrDollar::None)
173        }
174        _ => Ok(UsePathOrDollar::None),
175    }
176}
177
178/// Cycle handling for [UseSemantic::priv_use_semantic_data].
179fn priv_use_semantic_data_cycle<'db>(
180    db: &'db dyn Database,
181    _id: salsa::Id,
182    use_id: UseId<'db>,
183) -> Maybe<Arc<UseData<'db>>> {
184    let module_id = use_id.parent_module(db);
185    let mut diagnostics = SemanticDiagnostics::new(module_id);
186    let use_ast = db.module_use_by_id(use_id)?;
187    let err = Err(diagnostics.report(use_ast.stable_ptr(db), UseCycle));
188    let inference_id =
189        InferenceId::LookupItemDeclaration(LookupItemId::ModuleItem(ModuleItemId::Use(use_id)));
190    Ok(Arc::new(UseData {
191        diagnostics: diagnostics.build(),
192        resolved_item: err,
193        resolver_data: Arc::new(ResolverData::new(module_id, inference_id)),
194    }))
195}
196
197/// Implementation of [UseSemantic::use_semantic_diagnostics].
198fn use_semantic_diagnostics<'db>(
199    db: &'db dyn Database,
200    use_id: UseId<'db>,
201) -> Diagnostics<'db, SemanticDiagnostic<'db>> {
202    db.priv_use_semantic_data(use_id).map(|data| data.diagnostics.clone()).unwrap_or_default()
203}
204
205/// Query implementation of [UseSemantic::use_semantic_diagnostics].
206#[salsa::tracked]
207fn use_semantic_diagnostics_tracked<'db>(
208    db: &'db dyn Database,
209    use_id: UseId<'db>,
210) -> Diagnostics<'db, SemanticDiagnostic<'db>> {
211    use_semantic_diagnostics(db, use_id)
212}
213
214/// Implementation of [UseSemantic::use_resolver_data].
215fn use_resolver_data<'db>(
216    db: &'db dyn Database,
217    use_id: UseId<'db>,
218) -> Maybe<Arc<ResolverData<'db>>> {
219    Ok(db.priv_use_semantic_data(use_id)?.resolver_data.clone())
220}
221
222/// Query implementation of [UseSemantic::use_resolver_data].
223#[salsa::tracked(cycle_result=use_resolver_data_cycle)]
224fn use_resolver_data_tracked<'db>(
225    db: &'db dyn Database,
226    use_id: UseId<'db>,
227) -> Maybe<Arc<ResolverData<'db>>> {
228    use_resolver_data(db, use_id)
229}
230
231/// Trivial cycle handler for [UseSemantic::use_resolver_data].
232fn use_resolver_data_cycle<'db>(
233    db: &'db dyn Database,
234    _id: salsa::Id,
235    use_id: UseId<'db>,
236) -> Maybe<Arc<ResolverData<'db>>> {
237    // Forwarding (not as a query) cycle handling to `priv_use_semantic_data` cycle handler.
238    use_resolver_data(db, use_id)
239}
240
241pub trait SemanticUseEx<'a>: Database {
242    /// Returns the resolved item or an error if it can't be resolved.
243    ///
244    /// This is not a query as the cycle handling is done in priv_use_semantic_data.
245    fn use_resolved_item(&'a self, use_id: UseId<'a>) -> Maybe<ResolvedGenericItem<'a>> {
246        let db = self.as_dyn_database();
247        db.priv_use_semantic_data(use_id)?.resolved_item.clone()
248    }
249}
250
251impl<'a, T: Database + ?Sized> SemanticUseEx<'a> for T {}
252
253#[derive(Clone, Debug, PartialEq, Eq, DebugWithDb, salsa::Update)]
254#[debug_db(dyn Database)]
255pub struct UseGlobalData<'db> {
256    diagnostics: Diagnostics<'db, SemanticDiagnostic<'db>>,
257    imported_module: Maybe<ModuleId<'db>>,
258}
259
260/// Implementation of [UseSemantic::priv_global_use_semantic_data].
261fn priv_global_use_semantic_data<'db>(
262    db: &'db dyn Database,
263    global_use_id: GlobalUseId<'db>,
264) -> Maybe<UseGlobalData<'db>> {
265    let module_id = global_use_id.parent_module(db);
266    let mut diagnostics = SemanticDiagnostics::new(module_id);
267    let inference_id = InferenceId::GlobalUseStar(global_use_id);
268    let star_ast = ast::UsePath::Star(db.module_global_use_by_id(global_use_id)?);
269    let mut resolver = Resolver::new(db, module_id, inference_id);
270    let edition = resolver.settings.edition;
271    if edition.ignore_visibility() {
272        // We block support for global use where visibility is ignored.
273        diagnostics.report(star_ast.stable_ptr(db), GlobalUsesNotSupportedInEdition(edition));
274    }
275
276    let item = star_ast.get_item(db);
277    let segments = get_use_path_segments(db, star_ast.clone())?;
278    let Some(last_segment) = segments.segments.last() else {
279        let imported_module = Err(diagnostics.report(star_ast.stable_ptr(db), UseStarEmptyPath));
280        return Ok(UseGlobalData { diagnostics: diagnostics.build(), imported_module });
281    };
282    let last_element_ptr = last_segment.stable_ptr(db);
283    resolver.set_feature_config(&global_use_id, &item, &mut diagnostics);
284    let imported_module = resolver
285        .resolve_generic_path(
286            &mut diagnostics,
287            segments,
288            NotFoundItemType::Identifier,
289            ResolutionContext::Default,
290        )
291        .and_then(|resolved_item| match &resolved_item {
292            ResolvedGenericItem::Module(module_id) => Ok(*module_id),
293            other => Err(diagnostics.report(
294                last_element_ptr,
295                UnexpectedElement { expected: vec![ElementKind::Module], actual: other.into() },
296            )),
297        });
298    if imported_module == Ok(module_id) {
299        diagnostics.report(star_ast.stable_ptr(db), SelfGlobalUse);
300    }
301    Ok(UseGlobalData { diagnostics: diagnostics.build(), imported_module })
302}
303
304/// Query implementation of [UseSemantic::priv_global_use_semantic_data].
305#[salsa::tracked(cycle_fn=priv_global_use_semantic_data_tracked_cycle_fn, cycle_initial=priv_global_use_semantic_data_tracked_initial)]
306fn priv_global_use_semantic_data_tracked<'db>(
307    db: &'db dyn Database,
308    global_use_id: GlobalUseId<'db>,
309) -> Maybe<UseGlobalData<'db>> {
310    priv_global_use_semantic_data(db, global_use_id)
311}
312
313/// Cycle handling for [UseSemantic::priv_global_use_semantic_data].
314fn priv_global_use_semantic_data_tracked_cycle_fn<'db>(
315    _db: &'db dyn Database,
316    _cycle: &salsa::Cycle<'_>,
317    _last_provisional_value: &Maybe<UseGlobalData<'db>>,
318    value: Maybe<UseGlobalData<'db>>,
319    _global_use_id: GlobalUseId<'db>,
320) -> Maybe<UseGlobalData<'db>> {
321    value
322}
323
324/// Cycle handling for [UseSemantic::priv_global_use_semantic_data].
325fn priv_global_use_semantic_data_tracked_initial<'db>(
326    db: &'db dyn Database,
327    _id: salsa::Id,
328    global_use_id: GlobalUseId<'db>,
329) -> Maybe<UseGlobalData<'db>> {
330    let mut diagnostics = SemanticDiagnostics::new(global_use_id.parent_module(db));
331    let global_use_ast = db.module_global_use_by_id(global_use_id)?;
332    let segments = get_use_path_segments(db, ast::UsePath::Star(global_use_ast.clone()))?;
333    let err = if segments.segments.len() == 1 {
334        // `use bad_name::*`, will attempt to find `bad_name` in the current module's global
335        // uses, which includes the global use `use bad_name::*` (itself) - but we don't want to
336        // report a cycle in this case.
337        diagnostics.report(
338            segments.segments.last().unwrap().stable_ptr(db),
339            PathNotFound(NotFoundItemType::Identifier),
340        )
341    } else {
342        diagnostics.report(global_use_ast.stable_ptr(db), UseCycle)
343    };
344    Ok(UseGlobalData { diagnostics: diagnostics.build(), imported_module: Err(err) })
345}
346
347/// Implementation of [UseSemantic::priv_global_use_imported_module].
348fn priv_global_use_imported_module<'db>(
349    db: &'db dyn Database,
350    global_use_id: GlobalUseId<'db>,
351) -> Maybe<ModuleId<'db>> {
352    db.priv_global_use_semantic_data(global_use_id)?.imported_module
353}
354
355/// Query implementation of [UseSemantic::priv_global_use_imported_module].
356#[salsa::tracked]
357fn priv_global_use_imported_module_tracked<'db>(
358    db: &'db dyn Database,
359    global_use_id: GlobalUseId<'db>,
360) -> Maybe<ModuleId<'db>> {
361    priv_global_use_imported_module(db, global_use_id)
362}
363
364/// Implementation of [UseSemantic::global_use_semantic_diagnostics].
365fn global_use_semantic_diagnostics<'db>(
366    db: &'db dyn Database,
367    global_use_id: GlobalUseId<'db>,
368) -> Diagnostics<'db, SemanticDiagnostic<'db>> {
369    db.priv_global_use_semantic_data(global_use_id).map(|data| data.diagnostics).unwrap_or_default()
370}
371
372/// Query implementation of [UseSemantic::global_use_semantic_diagnostics].
373#[salsa::tracked]
374fn global_use_semantic_diagnostics_tracked<'db>(
375    db: &'db dyn Database,
376    global_use_id: GlobalUseId<'db>,
377) -> Diagnostics<'db, SemanticDiagnostic<'db>> {
378    global_use_semantic_diagnostics(db, global_use_id)
379}
380
381/// The modules that are imported by a module, using global uses and macro calls.
382pub type ImportedModules<'db> = OrderedHashMap<ModuleId<'db>, ImportInfo<'db>>;
383
384/// Information about a module that is imported by a module, using global uses and macro calls.
385#[derive(Debug, Default, Clone, PartialEq, Eq, salsa::Update)]
386pub struct ImportInfo<'db> {
387    /// The modules that directly imported this module.
388    pub user_modules: Vec<ModuleId<'db>>,
389}
390
391/// Returns the modules that are imported with `use *` and macro calls in the current module.
392/// Query implementation of [UseSemantic::module_imported_modules].
393#[salsa::tracked(returns(ref),cycle_fn=module_imported_modules_cycle_fn, cycle_initial=module_imported_modules_initial)]
394fn module_imported_modules<'db>(
395    db: &'db dyn Database,
396    _tracked: Tracked,
397    module_id: ModuleId<'db>,
398) -> ImportedModules<'db> {
399    let mut visited = UnorderedHashSet::<_>::default();
400    let mut stack = vec![(module_id, module_id)];
401    let mut modules = OrderedHashMap::<ModuleId<'db>, ImportInfo<'db>>::default();
402    modules.insert(module_id, ImportInfo { user_modules: vec![module_id] });
403    // Iterate over all modules that are imported through `use *`, and are accessible from the
404    // current module.
405    while let Some((user_module, containing_module)) = stack.pop() {
406        if !visited.insert((user_module, containing_module)) {
407            continue;
408        }
409        for defined_module in
410            chain!([&containing_module], module_macro_modules(db, false, containing_module))
411        {
412            let Ok(glob_uses) = get_module_global_uses(db, *defined_module) else {
413                continue;
414            };
415            for (glob_use, item_visibility) in glob_uses.iter() {
416                let Ok(module_id_found) = db.priv_global_use_imported_module(*glob_use) else {
417                    continue;
418                };
419                // Add the module to the map to find all reachable modules.
420                let entry = modules.entry(module_id_found).or_default();
421                if peek_visible_in(db, *item_visibility, containing_module, user_module) {
422                    stack.push((containing_module, module_id_found));
423                    entry.user_modules.push(containing_module);
424                }
425            }
426        }
427    }
428    let mut stack =
429        modules.iter().filter(|(_, v)| v.user_modules.is_empty()).map(|(k, _)| *k).collect_vec();
430    // Iterate over all modules that are imported through `use *`.
431    while let Some(curr_module_id) = stack.pop() {
432        for defined_module in
433            chain!([&curr_module_id], module_macro_modules(db, false, curr_module_id))
434        {
435            let Ok(glob_uses) = get_module_global_uses(db, *defined_module) else { continue };
436            for glob_use in glob_uses.keys() {
437                if let Ok(module_id_found) = db.priv_global_use_imported_module(*glob_use)
438                    && let Entry::Vacant(entry) = modules.entry(module_id_found)
439                {
440                    entry.insert(ImportInfo::default());
441                    stack.push(module_id_found);
442                }
443            }
444        }
445    }
446    modules
447}
448
449/// Cycle handling for [UseSemantic::module_imported_modules].
450fn module_imported_modules_cycle_fn<'db>(
451    _db: &dyn Database,
452    _cycle: &salsa::Cycle<'_>,
453    _last_provisional_value: &ImportedModules<'db>,
454    value: ImportedModules<'db>,
455    _tracked: Tracked,
456    _module_id: ModuleId<'db>,
457) -> ImportedModules<'db> {
458    value
459}
460
461/// Cycle handling for [UseSemantic::module_imported_modules].
462fn module_imported_modules_initial<'db>(
463    _db: &'db dyn Database,
464    _id: salsa::Id,
465    _tracked: Tracked,
466    _module_id: ModuleId<'db>,
467) -> ImportedModules<'db> {
468    Default::default()
469}
470
471/// Trait for use-related semantic queries.
472pub trait UseSemantic<'db>: Database {
473    /// Private query to compute data about a use.
474    fn priv_use_semantic_data(&'db self, use_id: UseId<'db>) -> Maybe<Arc<UseData<'db>>> {
475        priv_use_semantic_data_tracked(self.as_dyn_database(), use_id)
476    }
477    /// Returns the semantic diagnostics of a use.
478    fn use_semantic_diagnostics(
479        &'db self,
480        use_id: UseId<'db>,
481    ) -> Diagnostics<'db, SemanticDiagnostic<'db>> {
482        use_semantic_diagnostics_tracked(self.as_dyn_database(), use_id)
483    }
484    /// Returns the resolver data of a use.
485    fn use_resolver_data(&'db self, use_id: UseId<'db>) -> Maybe<Arc<ResolverData<'db>>> {
486        use_resolver_data_tracked(self.as_dyn_database(), use_id)
487    }
488    /// Private query to compute data about a global use.
489    fn priv_global_use_semantic_data(
490        &'db self,
491        global_use_id: GlobalUseId<'db>,
492    ) -> Maybe<UseGlobalData<'db>> {
493        priv_global_use_semantic_data_tracked(self.as_dyn_database(), global_use_id)
494    }
495    /// Private query to compute the imported module, given a global use.
496    fn priv_global_use_imported_module(
497        &'db self,
498        global_use_id: GlobalUseId<'db>,
499    ) -> Maybe<ModuleId<'db>> {
500        priv_global_use_imported_module_tracked(self.as_dyn_database(), global_use_id)
501    }
502    /// Returns the semantic diagnostics of a global use.
503    fn global_use_semantic_diagnostics(
504        &'db self,
505        global_use_id: GlobalUseId<'db>,
506    ) -> Diagnostics<'db, SemanticDiagnostic<'db>> {
507        global_use_semantic_diagnostics_tracked(self.as_dyn_database(), global_use_id)
508    }
509    /// Computes the imported modules of a module, using global uses and macro calls.
510    fn module_imported_modules(
511        &'db self,
512        _tracked: Tracked,
513        module_id: ModuleId<'db>,
514    ) -> &'db ImportedModules<'db> {
515        module_imported_modules(self.as_dyn_database(), _tracked, module_id)
516    }
517}
518impl<'db, T: Database + ?Sized> UseSemantic<'db> for T {}