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.module_id(db);
57    let mut diagnostics = SemanticDiagnostics::default();
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    use_id: UseId<'db>,
182) -> Maybe<Arc<UseData<'db>>> {
183    let module_id = use_id.module_id(db);
184    let mut diagnostics = SemanticDiagnostics::default();
185    let use_ast = db.module_use_by_id(use_id)?;
186    let err = Err(diagnostics.report(use_ast.stable_ptr(db), UseCycle));
187    let inference_id =
188        InferenceId::LookupItemDeclaration(LookupItemId::ModuleItem(ModuleItemId::Use(use_id)));
189    Ok(Arc::new(UseData {
190        diagnostics: diagnostics.build(),
191        resolved_item: err,
192        resolver_data: Arc::new(ResolverData::new(module_id, inference_id)),
193    }))
194}
195
196/// Implementation of [UseSemantic::use_semantic_diagnostics].
197fn use_semantic_diagnostics<'db>(
198    db: &'db dyn Database,
199    use_id: UseId<'db>,
200) -> Diagnostics<'db, SemanticDiagnostic<'db>> {
201    db.priv_use_semantic_data(use_id).map(|data| data.diagnostics.clone()).unwrap_or_default()
202}
203
204/// Query implementation of [UseSemantic::use_semantic_diagnostics].
205#[salsa::tracked]
206fn use_semantic_diagnostics_tracked<'db>(
207    db: &'db dyn Database,
208    use_id: UseId<'db>,
209) -> Diagnostics<'db, SemanticDiagnostic<'db>> {
210    use_semantic_diagnostics(db, use_id)
211}
212
213/// Implementation of [UseSemantic::use_resolver_data].
214fn use_resolver_data<'db>(
215    db: &'db dyn Database,
216    use_id: UseId<'db>,
217) -> Maybe<Arc<ResolverData<'db>>> {
218    Ok(db.priv_use_semantic_data(use_id)?.resolver_data.clone())
219}
220
221/// Query implementation of [UseSemantic::use_resolver_data].
222#[salsa::tracked(cycle_result=use_resolver_data_cycle)]
223fn use_resolver_data_tracked<'db>(
224    db: &'db dyn Database,
225    use_id: UseId<'db>,
226) -> Maybe<Arc<ResolverData<'db>>> {
227    use_resolver_data(db, use_id)
228}
229
230/// Trivial cycle handler for [UseSemantic::use_resolver_data].
231fn use_resolver_data_cycle<'db>(
232    db: &'db dyn Database,
233    use_id: UseId<'db>,
234) -> Maybe<Arc<ResolverData<'db>>> {
235    // Forwarding (not as a query) cycle handling to `priv_use_semantic_data` cycle handler.
236    use_resolver_data(db, use_id)
237}
238
239pub trait SemanticUseEx<'a>: Database {
240    /// Returns the resolved item or an error if it can't be resolved.
241    ///
242    /// This is not a query as the cycle handling is done in priv_use_semantic_data.
243    fn use_resolved_item(&'a self, use_id: UseId<'a>) -> Maybe<ResolvedGenericItem<'a>> {
244        let db = self.as_dyn_database();
245        db.priv_use_semantic_data(use_id)?.resolved_item.clone()
246    }
247}
248
249impl<'a, T: Database + ?Sized> SemanticUseEx<'a> for T {}
250
251#[derive(Clone, Debug, PartialEq, Eq, DebugWithDb, salsa::Update)]
252#[debug_db(dyn Database)]
253pub struct UseGlobalData<'db> {
254    diagnostics: Diagnostics<'db, SemanticDiagnostic<'db>>,
255    imported_module: Maybe<ModuleId<'db>>,
256}
257
258/// Implementation of [UseSemantic::priv_global_use_semantic_data].
259fn priv_global_use_semantic_data<'db>(
260    db: &'db dyn Database,
261    global_use_id: GlobalUseId<'db>,
262) -> Maybe<UseGlobalData<'db>> {
263    let module_id = global_use_id.module_id(db);
264    let mut diagnostics = SemanticDiagnostics::default();
265    let inference_id = InferenceId::GlobalUseStar(global_use_id);
266    let star_ast = ast::UsePath::Star(db.module_global_use_by_id(global_use_id)?);
267    let mut resolver = Resolver::new(db, module_id, inference_id);
268    let edition = resolver.settings.edition;
269    if edition.ignore_visibility() {
270        // We block support for global use where visibility is ignored.
271        diagnostics.report(star_ast.stable_ptr(db), GlobalUsesNotSupportedInEdition(edition));
272    }
273
274    let item = star_ast.get_item(db);
275    let segments = get_use_path_segments(db, star_ast.clone())?;
276    let Some(last_segment) = segments.segments.last() else {
277        let imported_module = Err(diagnostics.report(star_ast.stable_ptr(db), UseStarEmptyPath));
278        return Ok(UseGlobalData { diagnostics: diagnostics.build(), imported_module });
279    };
280    let last_element_ptr = last_segment.stable_ptr(db);
281    resolver.set_feature_config(&global_use_id, &item, &mut diagnostics);
282    let imported_module = resolver
283        .resolve_generic_path(
284            &mut diagnostics,
285            segments,
286            NotFoundItemType::Identifier,
287            ResolutionContext::Default,
288        )
289        .and_then(|resolved_item| match &resolved_item {
290            ResolvedGenericItem::Module(module_id) => Ok(*module_id),
291            other => Err(diagnostics.report(
292                last_element_ptr,
293                UnexpectedElement { expected: vec![ElementKind::Module], actual: other.into() },
294            )),
295        });
296    if imported_module == Ok(module_id) {
297        diagnostics.report(star_ast.stable_ptr(db), SelfGlobalUse);
298    }
299    Ok(UseGlobalData { diagnostics: diagnostics.build(), imported_module })
300}
301
302/// Query implementation of [UseSemantic::priv_global_use_semantic_data].
303#[salsa::tracked(cycle_fn=priv_global_use_semantic_data_tracked_cycle_fn, cycle_initial=priv_global_use_semantic_data_tracked_initial)]
304fn priv_global_use_semantic_data_tracked<'db>(
305    db: &'db dyn Database,
306    global_use_id: GlobalUseId<'db>,
307) -> Maybe<UseGlobalData<'db>> {
308    priv_global_use_semantic_data(db, global_use_id)
309}
310
311/// Cycle handling for [UseSemantic::priv_global_use_semantic_data].
312fn priv_global_use_semantic_data_tracked_cycle_fn<'db>(
313    _db: &'db dyn Database,
314    _value: &Maybe<UseGlobalData<'db>>,
315    _count: u32,
316    _global_use_id: GlobalUseId<'db>,
317) -> salsa::CycleRecoveryAction<Maybe<UseGlobalData<'db>>> {
318    salsa::CycleRecoveryAction::Iterate
319}
320
321/// Cycle handling for [UseSemantic::priv_global_use_semantic_data].
322fn priv_global_use_semantic_data_tracked_initial<'db>(
323    db: &'db dyn Database,
324    global_use_id: GlobalUseId<'db>,
325) -> Maybe<UseGlobalData<'db>> {
326    let mut diagnostics = SemanticDiagnostics::default();
327    let global_use_ast = db.module_global_use_by_id(global_use_id)?;
328    let star_ast = ast::UsePath::Star(db.module_global_use_by_id(global_use_id)?);
329    let segments = get_use_path_segments(db, star_ast)?;
330    let err = if segments.segments.len() == 1 {
331        // `use bad_name::*`, will attempt to find `bad_name` in the current module's global
332        // uses, which includes the global use `use bad_name::*` (itself) - but we don't want to
333        // report a cycle in this case.
334        diagnostics.report(
335            segments.segments.last().unwrap().stable_ptr(db),
336            PathNotFound(NotFoundItemType::Identifier),
337        )
338    } else {
339        diagnostics.report(global_use_ast.stable_ptr(db), UseCycle)
340    };
341    Ok(UseGlobalData { diagnostics: diagnostics.build(), imported_module: Err(err) })
342}
343
344/// Implementation of [UseSemantic::priv_global_use_imported_module].
345fn priv_global_use_imported_module<'db>(
346    db: &'db dyn Database,
347    global_use_id: GlobalUseId<'db>,
348) -> Maybe<ModuleId<'db>> {
349    db.priv_global_use_semantic_data(global_use_id)?.imported_module
350}
351
352/// Query implementation of [UseSemantic::priv_global_use_imported_module].
353#[salsa::tracked]
354fn priv_global_use_imported_module_tracked<'db>(
355    db: &'db dyn Database,
356    global_use_id: GlobalUseId<'db>,
357) -> Maybe<ModuleId<'db>> {
358    priv_global_use_imported_module(db, global_use_id)
359}
360
361/// Implementation of [UseSemantic::global_use_semantic_diagnostics].
362fn global_use_semantic_diagnostics<'db>(
363    db: &'db dyn Database,
364    global_use_id: GlobalUseId<'db>,
365) -> Diagnostics<'db, SemanticDiagnostic<'db>> {
366    db.priv_global_use_semantic_data(global_use_id).map(|data| data.diagnostics).unwrap_or_default()
367}
368
369/// Query implementation of [UseSemantic::global_use_semantic_diagnostics].
370#[salsa::tracked]
371fn global_use_semantic_diagnostics_tracked<'db>(
372    db: &'db dyn Database,
373    global_use_id: GlobalUseId<'db>,
374) -> Diagnostics<'db, SemanticDiagnostic<'db>> {
375    global_use_semantic_diagnostics(db, global_use_id)
376}
377
378/// The modules that are imported by a module, using global uses and macro calls.
379pub type ImportedModules<'db> = OrderedHashMap<ModuleId<'db>, ImportInfo<'db>>;
380
381/// Information about a module that is imported by a module, using global uses and macro calls.
382#[derive(Debug, Default, Clone, PartialEq, Eq, salsa::Update)]
383pub struct ImportInfo<'db> {
384    /// The modules that directly imported this module.
385    pub user_modules: Vec<ModuleId<'db>>,
386}
387
388/// Returns the modules that are imported with `use *` and macro calls in the current module.
389/// Query implementation of [UseSemantic::module_imported_modules].
390#[salsa::tracked(returns(ref),cycle_fn=module_imported_modules_cycle_fn, cycle_initial=module_imported_modules_initial)]
391fn module_imported_modules<'db>(
392    db: &'db dyn Database,
393    _tracked: Tracked,
394    module_id: ModuleId<'db>,
395) -> ImportedModules<'db> {
396    let mut visited = UnorderedHashSet::<_>::default();
397    let mut stack = vec![(module_id, module_id)];
398    let mut modules = OrderedHashMap::<ModuleId<'db>, ImportInfo<'db>>::default();
399    modules.insert(module_id, ImportInfo { user_modules: vec![module_id] });
400    // Iterate over all modules that are imported through `use *`, and are accessible from the
401    // current module.
402    while let Some((user_module, containing_module)) = stack.pop() {
403        if !visited.insert((user_module, containing_module)) {
404            continue;
405        }
406        for defined_module in
407            chain!([&containing_module], module_macro_modules(db, false, containing_module))
408        {
409            let Ok(glob_uses) = get_module_global_uses(db, *defined_module) else {
410                continue;
411            };
412            for (glob_use, item_visibility) in glob_uses.iter() {
413                let Ok(module_id_found) = db.priv_global_use_imported_module(*glob_use) else {
414                    continue;
415                };
416                // Add the module to the map to find all reachable modules.
417                let entry = modules.entry(module_id_found).or_default();
418                if peek_visible_in(db, *item_visibility, containing_module, user_module) {
419                    stack.push((containing_module, module_id_found));
420                    entry.user_modules.push(containing_module);
421                }
422            }
423        }
424    }
425    let mut stack =
426        modules.iter().filter(|(_, v)| v.user_modules.is_empty()).map(|(k, _)| *k).collect_vec();
427    // Iterate over all modules that are imported through `use *`.
428    while let Some(curr_module_id) = stack.pop() {
429        for defined_module in
430            chain!([&curr_module_id], module_macro_modules(db, false, curr_module_id))
431        {
432            let Ok(glob_uses) = get_module_global_uses(db, *defined_module) else { continue };
433            for glob_use in glob_uses.keys() {
434                if let Ok(module_id_found) = db.priv_global_use_imported_module(*glob_use)
435                    && let Entry::Vacant(entry) = modules.entry(module_id_found)
436                {
437                    entry.insert(ImportInfo::default());
438                    stack.push(module_id_found);
439                }
440            }
441        }
442    }
443    modules
444}
445
446/// Cycle handling for [UseSemantic::module_imported_modules].
447fn module_imported_modules_cycle_fn<'db>(
448    _db: &dyn Database,
449    _value: &ImportedModules<'db>,
450    _count: u32,
451    _tracked: Tracked,
452    _module_id: ModuleId<'db>,
453) -> salsa::CycleRecoveryAction<ImportedModules<'db>> {
454    salsa::CycleRecoveryAction::Iterate
455}
456
457/// Cycle handling for [UseSemantic::module_imported_modules].
458fn module_imported_modules_initial<'db>(
459    _db: &'db dyn Database,
460    _tracked: Tracked,
461    _module_id: ModuleId<'db>,
462) -> ImportedModules<'db> {
463    Default::default()
464}
465
466/// Trait for use-related semantic queries.
467pub trait UseSemantic<'db>: Database {
468    /// Private query to compute data about a use.
469    fn priv_use_semantic_data(&'db self, use_id: UseId<'db>) -> Maybe<Arc<UseData<'db>>> {
470        priv_use_semantic_data_tracked(self.as_dyn_database(), use_id)
471    }
472    /// Returns the semantic diagnostics of a use.
473    fn use_semantic_diagnostics(
474        &'db self,
475        use_id: UseId<'db>,
476    ) -> Diagnostics<'db, SemanticDiagnostic<'db>> {
477        use_semantic_diagnostics_tracked(self.as_dyn_database(), use_id)
478    }
479    /// Returns the resolver data of a use.
480    fn use_resolver_data(&'db self, use_id: UseId<'db>) -> Maybe<Arc<ResolverData<'db>>> {
481        use_resolver_data_tracked(self.as_dyn_database(), use_id)
482    }
483    /// Private query to compute data about a global use.
484    fn priv_global_use_semantic_data(
485        &'db self,
486        global_use_id: GlobalUseId<'db>,
487    ) -> Maybe<UseGlobalData<'db>> {
488        priv_global_use_semantic_data_tracked(self.as_dyn_database(), global_use_id)
489    }
490    /// Private query to compute the imported module, given a global use.
491    fn priv_global_use_imported_module(
492        &'db self,
493        global_use_id: GlobalUseId<'db>,
494    ) -> Maybe<ModuleId<'db>> {
495        priv_global_use_imported_module_tracked(self.as_dyn_database(), global_use_id)
496    }
497    /// Returns the semantic diagnostics of a global use.
498    fn global_use_semantic_diagnostics(
499        &'db self,
500        global_use_id: GlobalUseId<'db>,
501    ) -> Diagnostics<'db, SemanticDiagnostic<'db>> {
502        global_use_semantic_diagnostics_tracked(self.as_dyn_database(), global_use_id)
503    }
504    /// Computes the imported modules of a module, using global uses and macro calls.
505    fn module_imported_modules(
506        &'db self,
507        _tracked: Tracked,
508        module_id: ModuleId<'db>,
509    ) -> &'db ImportedModules<'db> {
510        module_imported_modules(self.as_dyn_database(), _tracked, module_id)
511    }
512}
513impl<'db, T: Database + ?Sized> UseSemantic<'db> for T {}