cairo_lang_semantic/items/
us.rs

1use std::sync::Arc;
2
3use cairo_lang_defs::ids::{
4    GlobalUseId, LanguageElementId, LookupItemId, ModuleId, ModuleItemId, UseId,
5};
6use cairo_lang_diagnostics::{Diagnostics, Maybe, ToMaybe};
7use cairo_lang_proc_macros::DebugWithDb;
8use cairo_lang_syntax::node::db::SyntaxGroup;
9use cairo_lang_syntax::node::helpers::UsePathEx;
10use cairo_lang_syntax::node::kind::SyntaxKind;
11use cairo_lang_syntax::node::{TypedSyntaxNode, ast};
12use cairo_lang_utils::Upcast;
13use cairo_lang_utils::ordered_hash_set::OrderedHashSet;
14use cairo_lang_utils::unordered_hash_set::UnorderedHashSet;
15
16use super::module::get_module_global_uses;
17use super::visibility::peek_visible_in;
18use crate::SemanticDiagnostic;
19use crate::db::SemanticGroup;
20use crate::diagnostic::SemanticDiagnosticKind::*;
21use crate::diagnostic::{
22    ElementKind, NotFoundItemType, SemanticDiagnostics, SemanticDiagnosticsBuilder,
23};
24use crate::expr::inference::InferenceId;
25use crate::resolve::{ResolvedGenericItem, Resolver, ResolverData};
26
27#[derive(Clone, Debug, PartialEq, Eq, DebugWithDb)]
28#[debug_db(dyn SemanticGroup + 'static)]
29pub struct UseData {
30    diagnostics: Diagnostics<SemanticDiagnostic>,
31    resolved_item: Maybe<ResolvedGenericItem>,
32    resolver_data: Arc<ResolverData>,
33}
34
35/// Query implementation of [crate::db::SemanticGroup::priv_use_semantic_data].
36pub fn priv_use_semantic_data(db: &dyn SemanticGroup, use_id: UseId) -> Maybe<UseData> {
37    let module_file_id = use_id.module_file_id(db.upcast());
38    let mut diagnostics = SemanticDiagnostics::default();
39    let inference_id =
40        InferenceId::LookupItemDeclaration(LookupItemId::ModuleItem(ModuleItemId::Use(use_id)));
41    let mut resolver = Resolver::new(db, module_file_id, inference_id);
42    // TODO(spapini): when code changes in a file, all the AST items change (as they contain a path
43    // to the green root that changes. Once ASTs are rooted on items, use a selector that picks only
44    // the item instead of all the module data.
45    let use_ast = ast::UsePath::Leaf(db.module_use_by_id(use_id)?.to_maybe()?);
46    let item = use_ast.get_item(db.upcast());
47    resolver.set_feature_config(&use_id, &item, &mut diagnostics);
48    let segments = get_use_path_segments(db.upcast(), use_ast)?;
49    let resolved_item = resolver.resolve_generic_path(
50        &mut diagnostics,
51        segments,
52        NotFoundItemType::Identifier,
53        None,
54    );
55    let resolver_data: Arc<ResolverData> = Arc::new(resolver.data);
56
57    Ok(UseData { diagnostics: diagnostics.build(), resolved_item, resolver_data })
58}
59
60/// Returns the segments that are the parts of the use path.
61///
62/// The segments are returned in the order they appear in the use path.
63/// Only `UsePathLeaf` and `UsePathSingle` are supported.
64///
65/// For example:
66/// Given the `c` of `use a::b::{c, d};` will return `[a, b, c]`.
67/// Given the `b` of `use a::b::{c, d};` will return `[a, b]`.
68pub fn get_use_path_segments(
69    db: &dyn SyntaxGroup,
70    use_path: ast::UsePath,
71) -> Maybe<Vec<ast::PathSegment>> {
72    let mut rev_segments = vec![];
73    match &use_path {
74        ast::UsePath::Leaf(use_ast) => rev_segments.push(use_ast.ident(db)),
75        ast::UsePath::Single(use_ast) => rev_segments.push(use_ast.ident(db)),
76        ast::UsePath::Star(_) => {}
77        ast::UsePath::Multi(_) => {
78            panic!("Only `UsePathLeaf` and `UsePathSingle` are supported.")
79        }
80    }
81    let mut current_use_path = use_path;
82    while let Some(parent_use_path) = get_parent_single_use_path(db, &current_use_path) {
83        rev_segments.push(parent_use_path.ident(db));
84        current_use_path = ast::UsePath::Single(parent_use_path);
85    }
86    Ok(rev_segments.into_iter().rev().collect())
87}
88
89/// Returns the parent `UsePathSingle` of a use path if exists.
90fn get_parent_single_use_path(
91    db: &dyn SyntaxGroup,
92    use_path: &ast::UsePath,
93) -> Option<ast::UsePathSingle> {
94    use SyntaxKind::*;
95    let mut node = use_path.as_syntax_node();
96    loop {
97        node = node.parent().expect("`UsePath` is not under an `ItemUse`.");
98        match node.kind(db) {
99            ItemUse => return None,
100            UsePathSingle => return Some(ast::UsePathSingle::from_syntax_node(db, node)),
101            UsePathList | UsePathMulti => continue,
102            UsePathLeaf => unreachable!("`UsePathLeaf` can't be a parent of another `UsePath`."),
103            other => unreachable!("`{other:?}` can't be a parent of `UsePath`."),
104        };
105    }
106}
107
108/// Cycle handling for [crate::db::SemanticGroup::priv_use_semantic_data].
109pub fn priv_use_semantic_data_cycle(
110    db: &dyn SemanticGroup,
111    cycle: &salsa::Cycle,
112    use_id: &UseId,
113) -> Maybe<UseData> {
114    let module_file_id = use_id.module_file_id(db.upcast());
115    let mut diagnostics = SemanticDiagnostics::default();
116    let use_ast = db.module_use_by_id(*use_id)?.to_maybe()?;
117    let err = Err(diagnostics.report(
118        &use_ast,
119        if cycle.participant_keys().count() == 1 {
120            // `use bad_name`, finds itself but we don't want to report a cycle in that case.
121            PathNotFound(NotFoundItemType::Identifier)
122        } else {
123            UseCycle
124        },
125    ));
126    let inference_id =
127        InferenceId::LookupItemDeclaration(LookupItemId::ModuleItem(ModuleItemId::Use(*use_id)));
128    Ok(UseData {
129        diagnostics: diagnostics.build(),
130        resolved_item: err,
131        resolver_data: Arc::new(ResolverData::new(module_file_id, inference_id)),
132    })
133}
134
135/// Query implementation of [crate::db::SemanticGroup::use_semantic_diagnostics].
136pub fn use_semantic_diagnostics(
137    db: &dyn SemanticGroup,
138    use_id: UseId,
139) -> Diagnostics<SemanticDiagnostic> {
140    db.priv_use_semantic_data(use_id).map(|data| data.diagnostics).unwrap_or_default()
141}
142
143/// Query implementation of [crate::db::SemanticGroup::use_resolver_data].
144pub fn use_resolver_data(db: &dyn SemanticGroup, use_id: UseId) -> Maybe<Arc<ResolverData>> {
145    Ok(db.priv_use_semantic_data(use_id)?.resolver_data)
146}
147
148/// Trivial cycle handler for [crate::db::SemanticGroup::use_resolver_data].
149pub fn use_resolver_data_cycle(
150    db: &dyn SemanticGroup,
151    _cycle: &salsa::Cycle,
152    use_id: &UseId,
153) -> Maybe<Arc<ResolverData>> {
154    // Forwarding (not as a query) cycle handling to `priv_use_semantic_data` cycle handler.
155    use_resolver_data(db, *use_id)
156}
157
158pub trait SemanticUseEx<'a>: Upcast<dyn SemanticGroup + 'a> {
159    /// Returns the resolved item or an error if it can't be resolved.
160    ///
161    /// This is not a query as the cycle handling is done in priv_use_semantic_data.
162    fn use_resolved_item(&self, use_id: UseId) -> Maybe<ResolvedGenericItem> {
163        let db = self.upcast();
164        db.priv_use_semantic_data(use_id)?.resolved_item
165    }
166}
167
168impl<'a, T: Upcast<dyn SemanticGroup + 'a> + ?Sized> SemanticUseEx<'a> for T {}
169
170#[derive(Clone, Debug, PartialEq, Eq, DebugWithDb)]
171#[debug_db(dyn SemanticGroup + 'static)]
172pub struct UseGlobalData {
173    diagnostics: Diagnostics<SemanticDiagnostic>,
174    imported_module: Maybe<ModuleId>,
175}
176
177/// Query implementation of [crate::db::SemanticGroup::priv_global_use_semantic_data].
178pub fn priv_global_use_semantic_data(
179    db: &dyn SemanticGroup,
180    global_use_id: GlobalUseId,
181) -> Maybe<UseGlobalData> {
182    let module_file_id = global_use_id.module_file_id(db.upcast());
183    let mut diagnostics = SemanticDiagnostics::default();
184    let inference_id = InferenceId::GlobalUseStar(global_use_id);
185    let star_ast = ast::UsePath::Star(db.module_global_use_by_id(global_use_id)?.to_maybe()?);
186    let mut resolver = Resolver::new(db, module_file_id, inference_id);
187    let edition = resolver.settings.edition;
188    if edition.ignore_visibility() {
189        // We block support for global use where visibility is ignored.
190        diagnostics.report(&star_ast, GlobalUsesNotSupportedInEdition(edition));
191    }
192
193    let item = star_ast.get_item(db.upcast());
194    let segments = get_use_path_segments(db.upcast(), star_ast.clone())?;
195    if segments.is_empty() {
196        let imported_module = Err(diagnostics.report(star_ast.stable_ptr(), UseStarEmptyPath));
197        return Ok(UseGlobalData { diagnostics: diagnostics.build(), imported_module });
198    }
199    resolver.set_feature_config(&global_use_id, &item, &mut diagnostics);
200    let resolved_item = resolver.resolve_generic_path(
201        &mut diagnostics,
202        segments.clone(),
203        NotFoundItemType::Identifier,
204        None,
205    )?;
206    // unwrap always safe as the resolver already resolved the entire path.
207    let last_segment = segments.last().unwrap();
208    let imported_module = match resolved_item {
209        ResolvedGenericItem::Module(module_id) => Ok(module_id),
210        _ => Err(diagnostics.report(
211            last_segment.stable_ptr(),
212            UnexpectedElement {
213                expected: vec![ElementKind::Module],
214                actual: (&resolved_item).into(),
215            },
216        )),
217    };
218    Ok(UseGlobalData { diagnostics: diagnostics.build(), imported_module })
219}
220
221/// Query implementation of [crate::db::SemanticGroup::priv_global_use_imported_module].
222pub fn priv_global_use_imported_module(
223    db: &dyn SemanticGroup,
224    global_use_id: GlobalUseId,
225) -> Maybe<ModuleId> {
226    db.priv_global_use_semantic_data(global_use_id)?.imported_module
227}
228
229/// Query implementation of [crate::db::SemanticGroup::global_use_semantic_diagnostics].
230pub fn global_use_semantic_diagnostics(
231    db: &dyn SemanticGroup,
232    global_use_id: GlobalUseId,
233) -> Diagnostics<SemanticDiagnostic> {
234    db.priv_global_use_semantic_data(global_use_id).map(|data| data.diagnostics).unwrap_or_default()
235}
236
237/// Cycle handling for [crate::db::SemanticGroup::priv_global_use_semantic_data].
238pub fn priv_global_use_semantic_data_cycle(
239    db: &dyn SemanticGroup,
240    cycle: &salsa::Cycle,
241    global_use_id: &GlobalUseId,
242) -> Maybe<UseGlobalData> {
243    let mut diagnostics = SemanticDiagnostics::default();
244    let global_use_ast = db.module_global_use_by_id(*global_use_id)?.to_maybe()?;
245    let star_ast = ast::UsePath::Star(db.module_global_use_by_id(*global_use_id)?.to_maybe()?);
246    let segments = get_use_path_segments(db.upcast(), star_ast)?;
247    let err = if cycle.participant_keys().count() <= 3 && segments.len() == 1 {
248        // `use bad_name::*`, will attempt to find `bad_name` in the current module's global
249        // uses, but which includes itself - but we don't want to report a cycle in this case.
250        diagnostics.report(
251            segments.last().unwrap().stable_ptr(),
252            PathNotFound(NotFoundItemType::Identifier),
253        )
254    } else {
255        diagnostics.report(&global_use_ast, UseCycle)
256    };
257    Ok(UseGlobalData { diagnostics: diagnostics.build(), imported_module: Err(err) })
258}
259
260/// The modules that are imported by a module, using global uses.
261#[derive(Debug, Clone, PartialEq, Eq)]
262pub struct ImportedModules {
263    /// The imported modules that have a path where each step is visible by the previous module.
264    pub accessible: OrderedHashSet<(ModuleId, ModuleId)>,
265    // TODO(Tomer-StarkWare): consider changing from all_modules to inaccessible_modules
266    /// All the imported modules.
267    pub all: OrderedHashSet<ModuleId>,
268}
269/// Returns the modules that are imported with `use *` in the current module.
270/// Query implementation of [crate::db::SemanticGroup::priv_module_use_star_modules].
271pub fn priv_module_use_star_modules(
272    db: &dyn SemanticGroup,
273    module_id: ModuleId,
274) -> Arc<ImportedModules> {
275    let mut visited = UnorderedHashSet::<_>::default();
276    let mut stack = vec![(module_id, module_id)];
277    let mut accessible_modules = OrderedHashSet::default();
278    // Iterate over all modules that are imported through `use *`, and are accessible from the
279    // current module.
280    while let Some((user_module, containing_module)) = stack.pop() {
281        if !visited.insert((user_module, containing_module)) {
282            continue;
283        }
284        let Ok(glob_uses) = get_module_global_uses(db, containing_module) else {
285            continue;
286        };
287        for (glob_use, item_visibility) in glob_uses.iter() {
288            let Ok(module_id_found) = db.priv_global_use_imported_module(*glob_use) else {
289                continue;
290            };
291            if peek_visible_in(db.upcast(), *item_visibility, containing_module, user_module) {
292                stack.push((containing_module, module_id_found));
293                accessible_modules.insert((containing_module, module_id_found));
294            }
295        }
296    }
297    let mut visited = UnorderedHashSet::<_>::default();
298    let mut stack = vec![module_id];
299    let mut all_modules = OrderedHashSet::default();
300    // Iterate over all modules that are imported through `use *`.
301    while let Some(curr_module_id) = stack.pop() {
302        if !visited.insert(curr_module_id) {
303            continue;
304        }
305        all_modules.insert(curr_module_id);
306        let Ok(glob_uses) = get_module_global_uses(db, curr_module_id) else { continue };
307        for glob_use in glob_uses.keys() {
308            let Ok(module_id_found) = db.priv_global_use_imported_module(*glob_use) else {
309                continue;
310            };
311            stack.push(module_id_found);
312        }
313    }
314    Arc::new(ImportedModules { accessible: accessible_modules, all: all_modules })
315}