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 star_ast = ast::UsePath::Star(db.module_global_use_by_id(global_use_id)?);
333    let segments = get_use_path_segments(db, star_ast)?;
334    let err = if segments.segments.len() == 1 {
335        // `use bad_name::*`, will attempt to find `bad_name` in the current module's global
336        // uses, which includes the global use `use bad_name::*` (itself) - but we don't want to
337        // report a cycle in this case.
338        diagnostics.report(
339            segments.segments.last().unwrap().stable_ptr(db),
340            PathNotFound(NotFoundItemType::Identifier),
341        )
342    } else {
343        diagnostics.report(global_use_ast.stable_ptr(db), UseCycle)
344    };
345    Ok(UseGlobalData { diagnostics: diagnostics.build(), imported_module: Err(err) })
346}
347
348/// Implementation of [UseSemantic::priv_global_use_imported_module].
349fn priv_global_use_imported_module<'db>(
350    db: &'db dyn Database,
351    global_use_id: GlobalUseId<'db>,
352) -> Maybe<ModuleId<'db>> {
353    db.priv_global_use_semantic_data(global_use_id)?.imported_module
354}
355
356/// Query implementation of [UseSemantic::priv_global_use_imported_module].
357#[salsa::tracked]
358fn priv_global_use_imported_module_tracked<'db>(
359    db: &'db dyn Database,
360    global_use_id: GlobalUseId<'db>,
361) -> Maybe<ModuleId<'db>> {
362    priv_global_use_imported_module(db, global_use_id)
363}
364
365/// Implementation of [UseSemantic::global_use_semantic_diagnostics].
366fn global_use_semantic_diagnostics<'db>(
367    db: &'db dyn Database,
368    global_use_id: GlobalUseId<'db>,
369) -> Diagnostics<'db, SemanticDiagnostic<'db>> {
370    db.priv_global_use_semantic_data(global_use_id).map(|data| data.diagnostics).unwrap_or_default()
371}
372
373/// Query implementation of [UseSemantic::global_use_semantic_diagnostics].
374#[salsa::tracked]
375fn global_use_semantic_diagnostics_tracked<'db>(
376    db: &'db dyn Database,
377    global_use_id: GlobalUseId<'db>,
378) -> Diagnostics<'db, SemanticDiagnostic<'db>> {
379    global_use_semantic_diagnostics(db, global_use_id)
380}
381
382/// The modules that are imported by a module, using global uses and macro calls.
383pub type ImportedModules<'db> = OrderedHashMap<ModuleId<'db>, ImportInfo<'db>>;
384
385/// Information about a module that is imported by a module, using global uses and macro calls.
386#[derive(Debug, Default, Clone, PartialEq, Eq, salsa::Update)]
387pub struct ImportInfo<'db> {
388    /// The modules that directly imported this module.
389    pub user_modules: Vec<ModuleId<'db>>,
390}
391
392/// Returns the modules that are imported with `use *` and macro calls in the current module.
393/// Query implementation of [UseSemantic::module_imported_modules].
394#[salsa::tracked(returns(ref),cycle_fn=module_imported_modules_cycle_fn, cycle_initial=module_imported_modules_initial)]
395fn module_imported_modules<'db>(
396    db: &'db dyn Database,
397    _tracked: Tracked,
398    module_id: ModuleId<'db>,
399) -> ImportedModules<'db> {
400    let mut visited = UnorderedHashSet::<_>::default();
401    let mut stack = vec![(module_id, module_id)];
402    let mut modules = OrderedHashMap::<ModuleId<'db>, ImportInfo<'db>>::default();
403    modules.insert(module_id, ImportInfo { user_modules: vec![module_id] });
404    // Iterate over all modules that are imported through `use *`, and are accessible from the
405    // current module.
406    while let Some((user_module, containing_module)) = stack.pop() {
407        if !visited.insert((user_module, containing_module)) {
408            continue;
409        }
410        for defined_module in
411            chain!([&containing_module], module_macro_modules(db, false, containing_module))
412        {
413            let Ok(glob_uses) = get_module_global_uses(db, *defined_module) else {
414                continue;
415            };
416            for (glob_use, item_visibility) in glob_uses.iter() {
417                let Ok(module_id_found) = db.priv_global_use_imported_module(*glob_use) else {
418                    continue;
419                };
420                // Add the module to the map to find all reachable modules.
421                let entry = modules.entry(module_id_found).or_default();
422                if peek_visible_in(db, *item_visibility, containing_module, user_module) {
423                    stack.push((containing_module, module_id_found));
424                    entry.user_modules.push(containing_module);
425                }
426            }
427        }
428    }
429    let mut stack =
430        modules.iter().filter(|(_, v)| v.user_modules.is_empty()).map(|(k, _)| *k).collect_vec();
431    // Iterate over all modules that are imported through `use *`.
432    while let Some(curr_module_id) = stack.pop() {
433        for defined_module in
434            chain!([&curr_module_id], module_macro_modules(db, false, curr_module_id))
435        {
436            let Ok(glob_uses) = get_module_global_uses(db, *defined_module) else { continue };
437            for glob_use in glob_uses.keys() {
438                if let Ok(module_id_found) = db.priv_global_use_imported_module(*glob_use)
439                    && let Entry::Vacant(entry) = modules.entry(module_id_found)
440                {
441                    entry.insert(ImportInfo::default());
442                    stack.push(module_id_found);
443                }
444            }
445        }
446    }
447    modules
448}
449
450/// Cycle handling for [UseSemantic::module_imported_modules].
451fn module_imported_modules_cycle_fn<'db>(
452    _db: &dyn Database,
453    _cycle: &salsa::Cycle<'_>,
454    _last_provisional_value: &ImportedModules<'db>,
455    value: ImportedModules<'db>,
456    _tracked: Tracked,
457    _module_id: ModuleId<'db>,
458) -> ImportedModules<'db> {
459    value
460}
461
462/// Cycle handling for [UseSemantic::module_imported_modules].
463fn module_imported_modules_initial<'db>(
464    _db: &'db dyn Database,
465    _id: salsa::Id,
466    _tracked: Tracked,
467    _module_id: ModuleId<'db>,
468) -> ImportedModules<'db> {
469    Default::default()
470}
471
472/// Trait for use-related semantic queries.
473pub trait UseSemantic<'db>: Database {
474    /// Private query to compute data about a use.
475    fn priv_use_semantic_data(&'db self, use_id: UseId<'db>) -> Maybe<Arc<UseData<'db>>> {
476        priv_use_semantic_data_tracked(self.as_dyn_database(), use_id)
477    }
478    /// Returns the semantic diagnostics of a use.
479    fn use_semantic_diagnostics(
480        &'db self,
481        use_id: UseId<'db>,
482    ) -> Diagnostics<'db, SemanticDiagnostic<'db>> {
483        use_semantic_diagnostics_tracked(self.as_dyn_database(), use_id)
484    }
485    /// Returns the resolver data of a use.
486    fn use_resolver_data(&'db self, use_id: UseId<'db>) -> Maybe<Arc<ResolverData<'db>>> {
487        use_resolver_data_tracked(self.as_dyn_database(), use_id)
488    }
489    /// Private query to compute data about a global use.
490    fn priv_global_use_semantic_data(
491        &'db self,
492        global_use_id: GlobalUseId<'db>,
493    ) -> Maybe<UseGlobalData<'db>> {
494        priv_global_use_semantic_data_tracked(self.as_dyn_database(), global_use_id)
495    }
496    /// Private query to compute the imported module, given a global use.
497    fn priv_global_use_imported_module(
498        &'db self,
499        global_use_id: GlobalUseId<'db>,
500    ) -> Maybe<ModuleId<'db>> {
501        priv_global_use_imported_module_tracked(self.as_dyn_database(), global_use_id)
502    }
503    /// Returns the semantic diagnostics of a global use.
504    fn global_use_semantic_diagnostics(
505        &'db self,
506        global_use_id: GlobalUseId<'db>,
507    ) -> Diagnostics<'db, SemanticDiagnostic<'db>> {
508        global_use_semantic_diagnostics_tracked(self.as_dyn_database(), global_use_id)
509    }
510    /// Computes the imported modules of a module, using global uses and macro calls.
511    fn module_imported_modules(
512        &'db self,
513        _tracked: Tracked,
514        module_id: ModuleId<'db>,
515    ) -> &'db ImportedModules<'db> {
516        module_imported_modules(self.as_dyn_database(), _tracked, module_id)
517    }
518}
519impl<'db, T: Database + ?Sized> UseSemantic<'db> for T {}