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(last_segment.stable_ptr(), UnexpectedElement {
211            expected: vec![ElementKind::Module],
212            actual: (&resolved_item).into(),
213        })),
214    };
215    Ok(UseGlobalData { diagnostics: diagnostics.build(), imported_module })
216}
217
218/// Query implementation of [crate::db::SemanticGroup::priv_global_use_imported_module].
219pub fn priv_global_use_imported_module(
220    db: &dyn SemanticGroup,
221    global_use_id: GlobalUseId,
222) -> Maybe<ModuleId> {
223    db.priv_global_use_semantic_data(global_use_id)?.imported_module
224}
225
226/// Query implementation of [crate::db::SemanticGroup::global_use_semantic_diagnostics].
227pub fn global_use_semantic_diagnostics(
228    db: &dyn SemanticGroup,
229    global_use_id: GlobalUseId,
230) -> Diagnostics<SemanticDiagnostic> {
231    db.priv_global_use_semantic_data(global_use_id).map(|data| data.diagnostics).unwrap_or_default()
232}
233
234/// Cycle handling for [crate::db::SemanticGroup::priv_global_use_semantic_data].
235pub fn priv_global_use_semantic_data_cycle(
236    db: &dyn SemanticGroup,
237    cycle: &salsa::Cycle,
238    global_use_id: &GlobalUseId,
239) -> Maybe<UseGlobalData> {
240    let mut diagnostics = SemanticDiagnostics::default();
241    let global_use_ast = db.module_global_use_by_id(*global_use_id)?.to_maybe()?;
242    let star_ast = ast::UsePath::Star(db.module_global_use_by_id(*global_use_id)?.to_maybe()?);
243    let segments = get_use_path_segments(db.upcast(), star_ast)?;
244    let err = if cycle.participant_keys().count() <= 3 && segments.len() == 1 {
245        // `use bad_name::*`, will attempt to find `bad_name` in the current module's global
246        // uses, but which includes itself - but we don't want to report a cycle in this case.
247        diagnostics.report(
248            segments.last().unwrap().stable_ptr(),
249            PathNotFound(NotFoundItemType::Identifier),
250        )
251    } else {
252        diagnostics.report(&global_use_ast, UseCycle)
253    };
254    Ok(UseGlobalData { diagnostics: diagnostics.build(), imported_module: Err(err) })
255}
256
257/// The modules that are imported by a module, using global uses.
258#[derive(Debug, Clone, PartialEq, Eq)]
259pub struct ImportedModules {
260    /// The imported modules that have a path where each step is visible by the previous module.
261    pub accessible: OrderedHashSet<(ModuleId, ModuleId)>,
262    // TODO(Tomer-StarkWare): consider changing from all_modules to inaccessible_modules
263    /// All the imported modules.
264    pub all: OrderedHashSet<ModuleId>,
265}
266/// Returns the modules that are imported with `use *` in the current module.
267/// Query implementation of [crate::db::SemanticGroup::priv_module_use_star_modules].
268pub fn priv_module_use_star_modules(
269    db: &dyn SemanticGroup,
270    module_id: ModuleId,
271) -> Arc<ImportedModules> {
272    let mut visited = UnorderedHashSet::<_>::default();
273    let mut stack = vec![(module_id, module_id)];
274    let mut accessible_modules = OrderedHashSet::default();
275    // Iterate over all modules that are imported through `use *`, and are accessible from the
276    // current module.
277    while let Some((user_module, containing_module)) = stack.pop() {
278        if !visited.insert((user_module, containing_module)) {
279            continue;
280        }
281        let Ok(glob_uses) = get_module_global_uses(db, containing_module) else {
282            continue;
283        };
284        for (glob_use, item_visibility) in glob_uses.iter() {
285            let Ok(module_id_found) = db.priv_global_use_imported_module(*glob_use) else {
286                continue;
287            };
288            if peek_visible_in(db.upcast(), *item_visibility, containing_module, user_module) {
289                stack.push((containing_module, module_id_found));
290                accessible_modules.insert((containing_module, module_id_found));
291            }
292        }
293    }
294    let mut visited = UnorderedHashSet::<_>::default();
295    let mut stack = vec![module_id];
296    let mut all_modules = OrderedHashSet::default();
297    // Iterate over all modules that are imported through `use *`.
298    while let Some(curr_module_id) = stack.pop() {
299        if !visited.insert(curr_module_id) {
300            continue;
301        }
302        all_modules.insert(curr_module_id);
303        let Ok(glob_uses) = get_module_global_uses(db, curr_module_id) else { continue };
304        for glob_use in glob_uses.keys() {
305            let Ok(module_id_found) = db.priv_global_use_imported_module(*glob_use) else {
306                continue;
307            };
308            stack.push(module_id_found);
309        }
310    }
311    Arc::new(ImportedModules { accessible: accessible_modules, all: all_modules })
312}