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