Skip to main content

cairo_lint/
corelib.rs

1use cairo_lang_defs::ids::{EnumId, TraitId};
2use cairo_lang_defs::{
3    db::DefsGroup,
4    ids::{
5        ExternFunctionId, FreeFunctionId, ImplDefId, ImplItemId, LookupItemId, ModuleId,
6        ModuleItemId, SubmoduleId, TopLevelLanguageElementId, TraitFunctionId, TraitItemId,
7    },
8};
9use cairo_lang_filesystem::ids::CrateId;
10use cairo_lang_semantic::helper::ModuleHelper;
11use cairo_lang_semantic::items::imp::ImplSemantic;
12use cairo_lang_semantic::items::trt::TraitSemantic;
13use cairo_lang_utils::ordered_hash_map::OrderedHashMap;
14use salsa::{Database, Update};
15
16pub const BOOL_PARTIAL_EQ_PATH: &str = "core::BoolPartialEq";
17pub const PANIC_PATH: &str = "core::panics::panic";
18pub const PANIC_WITH_BYTE_ARRAY_PATH: &str = "core::panics::panic_with_byte_array";
19pub const T_COPY_CLONE_PATH: &str = "core::clone::TCopyClone";
20pub const PARTIAL_ORD_LE_PATH: &str = "core::traits::PartialOrd::le";
21pub const PARTIAL_ORD_GE_PATH: &str = "core::traits::PartialOrd::ge";
22pub const ADD_TRAIT_FUNCTION_PATH: &str = "core::traits::Add::add";
23pub const SUB_TRAIT_FUNCTION_PATH: &str = "core::traits::Sub::sub";
24pub const INTEGER_MODULE_PATH: &str = "core::integer";
25pub const INTO_TRAIT_FUNCTION_PATH: &str = "core::traits::Into::into";
26pub const TRY_INTO_TRAIT_FUNCTION_PATH: &str = "core::traits::TryInto::try_into";
27pub const OPTION_TYPE_PATH: &str = "core::option::Option";
28
29static CORELIB_ITEM_PATHS: [&str; 12] = [
30    BOOL_PARTIAL_EQ_PATH,
31    PANIC_PATH,
32    PANIC_WITH_BYTE_ARRAY_PATH,
33    T_COPY_CLONE_PATH,
34    PARTIAL_ORD_LE_PATH,
35    PARTIAL_ORD_GE_PATH,
36    ADD_TRAIT_FUNCTION_PATH,
37    SUB_TRAIT_FUNCTION_PATH,
38    INTEGER_MODULE_PATH,
39    OPTION_TYPE_PATH,
40    INTO_TRAIT_FUNCTION_PATH,
41    TRY_INTO_TRAIT_FUNCTION_PATH,
42];
43
44#[derive(PartialEq, Eq, Hash, Debug, Clone, Update)]
45pub struct CorelibContext<'db> {
46    corelib_items: OrderedHashMap<String, Option<LookupItemId<'db>>>,
47}
48
49impl<'db> CorelibContext<'db> {
50    pub(crate) fn new(db: &'db dyn Database) -> Self {
51        let core_crate_id = CrateId::core(db);
52        let modules = db.crate_modules(core_crate_id);
53        Self {
54            corelib_items: CORELIB_ITEM_PATHS
55                .iter()
56                .map(|path| {
57                    for module in modules.iter() {
58                        let item_id = find_item_with_path(db, *module, path);
59                        if item_id.is_some() {
60                            return (path.to_string(), item_id);
61                        }
62                    }
63
64                    (path.to_string(), None)
65                })
66                .collect(),
67        }
68    }
69
70    // TODO (https://github.com/software-mansion/cairo-lint/issues/398): Write a macro for these getters to avoid boilerplate.
71    pub fn get_bool_partial_eq_impl_id(&self) -> ImplDefId<'db> {
72        let item = self
73            .corelib_items
74            .get(BOOL_PARTIAL_EQ_PATH)
75            .expect("Expected BoolPartialEq to be present in corelib items")
76            .expect("Expected BoolPartialEq to be defined in the corelib");
77        match item {
78            LookupItemId::ModuleItem(ModuleItemId::Impl(id)) => id,
79            _ => unreachable!("Expected BoolPartialEq to be an ImplDefId"),
80        }
81    }
82
83    pub fn get_panic_function_id(&self) -> ExternFunctionId<'db> {
84        let item = self
85            .corelib_items
86            .get(PANIC_PATH)
87            .expect("Expected panic to be present in corelib items")
88            .expect("Expected panic to be defined in the corelib");
89        match item {
90            LookupItemId::ModuleItem(ModuleItemId::ExternFunction(id)) => id,
91            _ => unreachable!("Expected panic to be a ExternFunction"),
92        }
93    }
94
95    pub fn get_panic_with_byte_array_function_id(&self) -> FreeFunctionId<'db> {
96        let item = self
97            .corelib_items
98            .get(PANIC_WITH_BYTE_ARRAY_PATH)
99            .expect("Expected panic_with_byte_array to be present in corelib items")
100            .expect("Expected panic_with_byte_array to be defined in the corelib");
101        match item {
102            LookupItemId::ModuleItem(ModuleItemId::FreeFunction(id)) => id,
103            _ => unreachable!("Expected panic_with_byte_array to be a FreeFunction"),
104        }
105    }
106
107    pub fn get_t_copy_clone_impl_id(&self) -> ImplDefId<'db> {
108        let item = self
109            .corelib_items
110            .get(T_COPY_CLONE_PATH)
111            .expect("Expected TCopyClone to be present in corelib items")
112            .expect("Expected TCopyClone to be defined in the corelib");
113        match item {
114            LookupItemId::ModuleItem(ModuleItemId::Impl(id)) => id,
115            _ => unreachable!("Expected TCopyClone to be an ImplDefId"),
116        }
117    }
118
119    pub fn get_partial_ord_le_trait_function_id(&self) -> TraitFunctionId<'db> {
120        let item = self
121            .corelib_items
122            .get(PARTIAL_ORD_LE_PATH)
123            .expect("Expected PartialOrd::le to be present in corelib items")
124            .expect("Expected PartialOrd::le to be defined in the corelib");
125        match item {
126            LookupItemId::TraitItem(TraitItemId::Function(id)) => id,
127            _ => unreachable!("Expected PartialOrd::le to be a TraitFunctionId"),
128        }
129    }
130
131    pub fn get_partial_ord_ge_trait_function_id(&self) -> TraitFunctionId<'db> {
132        let item = self
133            .corelib_items
134            .get(PARTIAL_ORD_GE_PATH)
135            .expect("Expected PartialOrd::ge to be present in corelib items")
136            .expect("Expected PartialOrd::ge to be defined in the corelib");
137        match item {
138            LookupItemId::TraitItem(TraitItemId::Function(id)) => id,
139            _ => unreachable!("Expected PartialOrd::ge to be a TraitFunctionId"),
140        }
141    }
142
143    pub fn get_add_trait_function_id(&self) -> TraitFunctionId<'db> {
144        let item = self
145            .corelib_items
146            .get(ADD_TRAIT_FUNCTION_PATH)
147            .expect("Expected Add::add to be present in corelib items")
148            .expect("Expected Add::add to be defined in the corelib");
149        match item {
150            LookupItemId::TraitItem(TraitItemId::Function(id)) => id,
151            _ => unreachable!("Expected Add::add to be a TraitFunctionId"),
152        }
153    }
154
155    pub fn get_sub_trait_function_id(&self) -> TraitFunctionId<'db> {
156        let item = self
157            .corelib_items
158            .get(SUB_TRAIT_FUNCTION_PATH)
159            .expect("Expected Sub::sub to be present in corelib items")
160            .expect("Expected Sub::sub to be defined in the corelib");
161        match item {
162            LookupItemId::TraitItem(TraitItemId::Function(id)) => id,
163            _ => unreachable!("Expected Sub::sub to be a TraitFunctionId"),
164        }
165    }
166
167    pub fn get_integer_module_id(&self) -> SubmoduleId<'db> {
168        let item = self
169            .corelib_items
170            .get(INTEGER_MODULE_PATH)
171            .expect("Expected integer module to be present in corelib items")
172            .expect("Expected integer module to be defined in the corelib");
173        match item {
174            LookupItemId::ModuleItem(ModuleItemId::Submodule(id)) => id,
175            _ => unreachable!("Expected integer module to be a Submodule"),
176        }
177    }
178
179    pub fn get_into_trait_function_id(&self) -> TraitFunctionId<'db> {
180        let item = self
181            .corelib_items
182            .get(INTO_TRAIT_FUNCTION_PATH)
183            .expect("Expected Into::into to be present in corelib items")
184            .expect("Expected Into::into to be defined in the corelib");
185        match item {
186            LookupItemId::TraitItem(TraitItemId::Function(id)) => id,
187            _ => unreachable!("Expected Into::into to be a TraitFunctionId"),
188        }
189    }
190
191    pub fn get_try_into_trait_function_id(&self) -> TraitFunctionId<'db> {
192        let item = self
193            .corelib_items
194            .get(TRY_INTO_TRAIT_FUNCTION_PATH)
195            .expect("Expected TryInto::try_into to be present in corelib items")
196            .expect("Expected TryInto::try_into to be defined in the corelib");
197        match item {
198            LookupItemId::TraitItem(TraitItemId::Function(id)) => id,
199            _ => unreachable!("Expected TryInto::try_into to be a TraitFunctionId"),
200        }
201    }
202    pub fn get_option_enum_id(&self) -> EnumId<'db> {
203        let item = self
204            .corelib_items
205            .get(OPTION_TYPE_PATH)
206            .expect("Expected Option to be present in corelib items")
207            .expect("Expected Option to be defined in the corelib");
208        match item {
209            LookupItemId::ModuleItem(ModuleItemId::Enum(id)) => id,
210            _ => unreachable!("Expected Option to be a EnumId"),
211        }
212    }
213
214    pub fn get_option_trait(&self, db: &'db dyn Database) -> TraitId<'db> {
215        ModuleHelper::core(db)
216            .submodule("option")
217            .trait_id("OptionTrait")
218    }
219
220    pub fn get_result_trait(&self, db: &'db dyn Database) -> TraitId<'db> {
221        ModuleHelper::core(db)
222            .submodule("result")
223            .trait_id("ResultTrait")
224    }
225}
226
227fn find_item_with_path<'db>(
228    db: &'db dyn Database,
229    module_id: ModuleId<'db>,
230    path: &str,
231) -> Option<LookupItemId<'db>> {
232    let items = module_id.module_data(db).ok()?.items(db);
233    for item in items.iter() {
234        if item.full_path(db) == path {
235            return Some(LookupItemId::ModuleItem(*item));
236        }
237        match item {
238            ModuleItemId::Submodule(submodule_id) => {
239                let submodule_item =
240                    find_item_with_path(db, ModuleId::Submodule(*submodule_id), path);
241                if submodule_item.is_some() {
242                    return submodule_item;
243                }
244            }
245            ModuleItemId::Impl(impl_id) => {
246                if let Ok(functions) = db.impl_functions(*impl_id) {
247                    for (_, impl_fn_id) in functions.iter() {
248                        if impl_fn_id.full_path(db) == path {
249                            return Some(LookupItemId::ImplItem(ImplItemId::Function(*impl_fn_id)));
250                        }
251                    }
252                }
253
254                if let Ok(types) = db.impl_types(*impl_id) {
255                    for (impl_type_id, _) in types.iter() {
256                        if impl_type_id.full_path(db) == path {
257                            return Some(LookupItemId::ImplItem(ImplItemId::Type(*impl_type_id)));
258                        }
259                    }
260                }
261
262                if let Ok(consts) = db.impl_constants(*impl_id) {
263                    for (impl_const_id, _) in consts.iter() {
264                        if impl_const_id.full_path(db) == path {
265                            return Some(LookupItemId::ImplItem(ImplItemId::Constant(
266                                *impl_const_id,
267                            )));
268                        }
269                    }
270                }
271            }
272            ModuleItemId::Trait(trait_id) => {
273                if let Ok(functions) = db.trait_functions(*trait_id) {
274                    for (_, trait_fn_id) in functions.iter() {
275                        if trait_fn_id.full_path(db) == path {
276                            return Some(LookupItemId::TraitItem(TraitItemId::Function(
277                                *trait_fn_id,
278                            )));
279                        }
280                    }
281                }
282
283                if let Ok(types) = db.trait_types(*trait_id) {
284                    for (_, trait_type_id) in types.iter() {
285                        if trait_type_id.full_path(db) == path {
286                            return Some(LookupItemId::TraitItem(TraitItemId::Type(
287                                *trait_type_id,
288                            )));
289                        }
290                    }
291                }
292
293                if let Ok(consts) = db.trait_constants(*trait_id) {
294                    for (_, trait_const_id) in consts.iter() {
295                        if trait_const_id.full_path(db) == path {
296                            return Some(LookupItemId::TraitItem(TraitItemId::Constant(
297                                *trait_const_id,
298                            )));
299                        }
300                    }
301                }
302            }
303            // The check for the item path happens before all the matches.
304            _ => (),
305        }
306    }
307    None
308}