1use cairo_lang_debug::DebugWithDb;
10use cairo_lang_defs::ids::{
11 ConstantId, EnumId, ExternFunctionId, ExternTypeId, FreeFunctionId, FunctionTitleId,
12 GenericTypeId, ImplAliasId, ImplDefId, ImplFunctionId, ImportableId, LanguageElementId,
13 MacroDeclarationId, ModuleId, ModuleItemId, ModuleTypeAliasId, NamedLanguageElementId,
14 StructId, SubmoduleId, TopLevelLanguageElementId, TraitFunctionId, TraitId, VariantId,
15};
16use cairo_lang_diagnostics::Maybe;
17use cairo_lang_filesystem::db::{FilesGroup, default_crate_settings};
18use cairo_lang_filesystem::ids::{CrateId, SmolStrId, Tracked};
19use itertools::{Itertools, chain};
20use salsa::Database;
21use tracing::trace;
22
23use crate::expr::inference::InferenceId;
24use crate::items::functions::GenericFunctionId;
25use crate::items::module::ModuleSemantic;
26use crate::resolve::{ResolvedGenericItem, Resolver};
27
28#[derive(Clone, Debug, PartialEq, Eq, salsa::SalsaValue)]
30pub struct ItemAccessInfo<'db> {
31 pub path_segments: Vec<ImportableId<'db>>,
33 pub access_kind: ItemAccessKind<'db>,
35 pub name: SmolStrId<'db>,
37}
38
39impl<'db> ItemAccessInfo<'db> {
40 pub fn new(
42 access_kind: ItemAccessKind<'db>,
43 path_segments: Vec<ImportableId<'db>>,
44 name: SmolStrId<'db>,
45 ) -> Self {
46 Self { path_segments, access_kind, name }
47 }
48
49 pub fn path(&self, db: &'db dyn Database) -> String {
51 let prefix = match self.access_kind {
52 ItemAccessKind::ViaCrate => "crate::",
53 ItemAccessKind::DirectInModule
54 | ItemAccessKind::ViaUse(_)
55 | ItemAccessKind::ViaPrelude
56 | ItemAccessKind::ViaGlobalUse
57 | ItemAccessKind::FullPath(_) => "",
58 };
59 let path = chain!(
61 self.path_segments.iter().rev().map(|id| id.name(db).long(db)),
62 [self.name.long(db)]
63 )
64 .format("::");
65 format!("{prefix}{path}")
66 }
67
68 pub fn segment_count(&self) -> usize {
70 self.path_segments.len()
71 }
72}
73
74#[derive(Clone, Debug, PartialEq, Eq, salsa::SalsaValue)]
76pub enum ItemAccessKind<'db> {
77 DirectInModule,
79 ViaUse(String),
81 ViaPrelude,
83 ViaGlobalUse,
85 ViaCrate,
87 FullPath(CrateId<'db>),
89}
90
91fn get_contextualized_path<'db>(
101 db: &'db dyn Database,
102 item: ImportableId<'db>,
103 context_module: ModuleId<'db>,
104) -> Maybe<ItemAccessInfo<'db>> {
105 if let ImportableId::Crate(crate_id) = item {
107 return Ok(ItemAccessInfo::new(ItemAccessKind::FullPath(crate_id), vec![], item.name(db)));
108 }
109
110 let mut resolver = Resolver::new(db, context_module, InferenceId::NoContext);
111
112 if let Some(access_info) = check_prelude(db, item, context_module, &mut resolver)? {
113 return Ok(access_info);
114 }
115
116 let item_name = item.name(db);
119 if let Some(info) = resolver.resolve_item_in_module(context_module, item_name)
120 && let Ok::<ImportableId<'_>, _>(found_item) = info.item_id.try_into()
121 && found_item == item
122 {
123 return Ok(ItemAccessInfo::new(ItemAccessKind::DirectInModule, vec![], item_name));
124 }
125
126 let (owning_crate, path_items) = ancestors(db, item);
127 for (i, ancestor) in path_items.iter().enumerate() {
128 if let Some(info) = resolver.resolve_item_in_module(context_module, ancestor.name(db))
131 && let Ok::<ImportableId<'_>, _>(found_item) = info.item_id.try_into()
132 && found_item == *ancestor
133 {
134 return Ok(ItemAccessInfo::new(
135 ItemAccessKind::DirectInModule,
136 path_items[..i + 1].iter().copied().collect_vec(),
137 item_name,
138 ));
139 }
140
141 if let Some(access_info) = check_prelude(db, *ancestor, context_module, &mut resolver)? {
143 let mut res_path_segments = path_items[..i + 1].iter().copied().collect_vec();
144 res_path_segments.extend(access_info.path_segments);
145 return Ok(ItemAccessInfo::new(
146 ItemAccessKind::ViaPrelude,
147 res_path_segments,
148 item_name,
149 ));
150 }
151 }
152
153 if owning_crate == context_module.owning_crate(db) {
154 let mut path_items = path_items;
157 path_items.pop();
158 return Ok(ItemAccessInfo::new(ItemAccessKind::ViaCrate, path_items, item_name));
159 }
160
161 Ok(ItemAccessInfo::new(ItemAccessKind::FullPath(owning_crate), path_items, item_name))
162}
163
164fn check_prelude<'db>(
167 db: &'db dyn Database,
168 item: ImportableId<'db>,
169 context_module: ModuleId<'db>,
170 resolver: &mut Resolver<'db>,
171) -> Maybe<Option<ItemAccessInfo<'db>>> {
172 let settings = db
173 .crate_config(context_module.owning_crate(db))
174 .map(|c| &c.settings)
175 .unwrap_or_else(|| default_crate_settings(db));
176 if let Some(prelude_module) = db.get_prelude_submodule(settings)
177 && let Some(module_item_id) = db.module_item_by_name(prelude_module, item.name(db))?
178 {
179 trace!("Found item {} in prelude module as {module_item_id:?}", item.name(db).long(db));
180 let is_prelude = match (item, ResolvedGenericItem::from_module_item(db, module_item_id)?) {
182 (ImportableId::Constant(id), ResolvedGenericItem::GenericConstant(resolved_id)) => {
183 id == resolved_id
184 }
185 (
186 ImportableId::Submodule(id),
187 ResolvedGenericItem::Module(ModuleId::Submodule(resolved_id)),
188 ) => id == resolved_id,
189 (
190 ImportableId::FreeFunction(id),
191 ResolvedGenericItem::GenericFunction(GenericFunctionId::Free(resolved_id)),
192 ) => id == resolved_id,
193 (
194 ImportableId::ExternFunction(id),
195 ResolvedGenericItem::GenericFunction(GenericFunctionId::Extern(resolved_id)),
196 ) => id == resolved_id,
197 (
198 ImportableId::Struct(id),
199 ResolvedGenericItem::GenericType(GenericTypeId::Struct(resolved_id)),
200 ) => id == resolved_id,
201 (
202 ImportableId::Enum(id),
203 ResolvedGenericItem::GenericType(GenericTypeId::Enum(resolved_id)),
204 ) => id == resolved_id,
205 (
206 ImportableId::ExternType(id),
207 ResolvedGenericItem::GenericType(GenericTypeId::Extern(resolved_id)),
208 ) => id == resolved_id,
209 (ImportableId::TypeAlias(id), ResolvedGenericItem::GenericTypeAlias(resolved_id)) => {
210 id == resolved_id
211 }
212 (ImportableId::ImplAlias(id), ResolvedGenericItem::GenericImplAlias(resolved_id)) => {
213 id == resolved_id
214 }
215 (ImportableId::Trait(id), ResolvedGenericItem::Trait(resolved_id)) => id == resolved_id,
216 (ImportableId::Impl(id), ResolvedGenericItem::Impl(resolved_id)) => id == resolved_id,
217 (ImportableId::MacroDeclaration(id), ResolvedGenericItem::Macro(resolved_id)) => {
218 id == resolved_id
219 }
220 (ImportableId::Variant(id), ResolvedGenericItem::Variant(resolved_id)) => {
221 id == resolved_id.id
222 }
223 _ => false,
224 };
225
226 if is_prelude {
227 if let Some(info) = resolver.resolve_item_in_module(context_module, item.name(db))
230 && !matches!(
231 info.item_id.try_into(),
232 Ok::<ImportableId<'_>, _>(found_item) if found_item == item
233 )
234 {
235 return Ok(None);
236 }
237 return Ok(Some(ItemAccessInfo::new(
238 ItemAccessKind::ViaPrelude,
239 vec![],
240 item.name(db),
241 )));
242 }
243 }
244 Ok(None)
245}
246
247fn ancestors<'db>(
250 db: &'db dyn Database,
251 item: ImportableId<'db>,
252) -> (CrateId<'db>, Vec<ImportableId<'db>>) {
253 let mut current = item;
254 let mut path = vec![];
255 while let Some(parent) = get_importable_parent(db, current) {
256 path.push(parent);
257 current = parent;
258 if let ImportableId::Crate(crate_id) = current {
259 return (crate_id, path);
260 }
261 }
262 unreachable!("Ancestor path must end with a crate root.")
263}
264
265fn get_importable_parent<'db>(
267 db: &'db dyn Database,
268 item: ImportableId<'db>,
269) -> Option<ImportableId<'db>> {
270 match item {
271 ImportableId::Variant(id) => Some(ImportableId::Enum(id.enum_id(db))),
272 _ => {
273 let mut module_id = item.parent_module(db)?;
274 loop {
275 match module_id {
276 ModuleId::CrateRoot(id) => return Some(ImportableId::Crate(id)),
277 ModuleId::Submodule(id) => return Some(ImportableId::Submodule(id)),
278 ModuleId::MacroCall { id, .. } => {
279 module_id = id.parent_module(db);
280 }
281 }
282 }
283 }
284 }
285}
286
287#[salsa::tracked(returns(clone))]
289fn contextualized_path_query<'db>(
290 db: &'db dyn Database,
291 _tracked: Tracked,
292 item: ImportableId<'db>,
293 context_module: ModuleId<'db>,
294) -> Maybe<ItemAccessInfo<'db>> {
295 let info = get_contextualized_path(db, item, context_module);
296 trace!(?item, ?context_module, ?info);
297 info
298}
299
300pub trait ContextualizePath<'db> {
302 fn contextualized_path(&self, db: &'db dyn Database, context_module: ModuleId<'db>) -> String;
304}
305
306trait IntoImportableMarker<'db>: Into<ImportableId<'db>> {}
308
309impl<'db, T> ContextualizePath<'db> for T
310where
311 for<'a> &'a T: IntoImportableMarker<'db>,
312 T: TopLevelLanguageElementId<'db>,
313{
314 fn contextualized_path(&self, db: &'db dyn Database, context_module: ModuleId<'db>) -> String {
315 if let Ok(info) = contextualized_path_query(db, (), self.into(), context_module) {
316 info.path(db)
317 } else {
318 self.full_path(db)
319 }
320 }
321}
322
323impl<'db> IntoImportableMarker<'db> for &SubmoduleId<'db> {}
324impl<'db> IntoImportableMarker<'db> for &ConstantId<'db> {}
325impl<'db> IntoImportableMarker<'db> for &FreeFunctionId<'db> {}
326impl<'db> IntoImportableMarker<'db> for &StructId<'db> {}
327impl<'db> IntoImportableMarker<'db> for &EnumId<'db> {}
328impl<'db> IntoImportableMarker<'db> for &VariantId<'db> {}
329impl<'db> IntoImportableMarker<'db> for &ModuleTypeAliasId<'db> {}
330impl<'db> IntoImportableMarker<'db> for &ImplAliasId<'db> {}
331impl<'db> IntoImportableMarker<'db> for &TraitId<'db> {}
332impl<'db> IntoImportableMarker<'db> for &ImplDefId<'db> {}
333impl<'db> IntoImportableMarker<'db> for &ExternTypeId<'db> {}
334impl<'db> IntoImportableMarker<'db> for &ExternFunctionId<'db> {}
335impl<'db> IntoImportableMarker<'db> for &MacroDeclarationId<'db> {}
336
337impl<'db> ContextualizePath<'db> for CrateId<'db> {
338 fn contextualized_path(&self, db: &'db dyn Database, context_module: ModuleId<'db>) -> String {
339 if let Ok(info) = contextualized_path_query(db, (), self.into(), context_module) {
340 info.path(db)
341 } else {
342 self.long(db).name().long(db).to_string()
343 }
344 }
345}
346
347impl<'db> ContextualizePath<'db> for ModuleId<'db> {
348 fn contextualized_path(&self, db: &'db dyn Database, context_module: ModuleId<'db>) -> String {
349 match self {
350 ModuleId::CrateRoot(crate_id) => crate_id.contextualized_path(db, context_module),
351 ModuleId::Submodule(submodule_id) => {
352 submodule_id.contextualized_path(db, context_module)
353 }
354 ModuleId::MacroCall { .. } => self.full_path(db),
355 }
356 }
357}
358
359impl<'db> ContextualizePath<'db> for ModuleItemId<'db> {
360 fn contextualized_path(&self, db: &'db dyn Database, context_module: ModuleId<'db>) -> String {
361 if let Ok(item) = (*self).try_into()
362 && let Ok(info) = contextualized_path_query(db, (), item, context_module)
363 {
364 info.path(db)
365 } else {
366 self.full_path(db)
367 }
368 }
369}
370
371impl<'db> ContextualizePath<'db> for TraitFunctionId<'db> {
372 fn contextualized_path(&self, db: &'db dyn Database, context_module: ModuleId<'db>) -> String {
373 let contextualized_path = self.trait_id(db).contextualized_path(db, context_module);
374 format!("{contextualized_path}::{}", self.name(db).long(db))
375 }
376}
377
378impl<'db> ContextualizePath<'db> for ImplFunctionId<'db> {
379 fn contextualized_path(&self, db: &'db dyn Database, context_module: ModuleId<'db>) -> String {
380 let contextualized_path = self.impl_def_id(db).contextualized_path(db, context_module);
381 format!("{contextualized_path}::{}", self.name(db).long(db))
382 }
383}
384
385impl<'db> ContextualizePath<'db> for FunctionTitleId<'db> {
386 fn contextualized_path(&self, db: &'db dyn Database, context_module: ModuleId<'db>) -> String {
387 match self {
388 FunctionTitleId::Free(id) => id.contextualized_path(db, context_module),
389 FunctionTitleId::Extern(id) => id.contextualized_path(db, context_module),
390 FunctionTitleId::Trait(id) => id.contextualized_path(db, context_module),
391 FunctionTitleId::Impl(id) => id.contextualized_path(db, context_module),
392 }
393 }
394}
395
396impl<'db> ContextualizePath<'db> for crate::ConcreteTraitId<'db> {
397 fn contextualized_path(&self, db: &'db dyn Database, context_module: ModuleId<'db>) -> String {
398 self.long(db).contextualized_path(db, context_module)
399 }
400}
401
402impl<'db> ContextualizePath<'db> for crate::items::trt::ConcreteTraitLongId<'db> {
403 fn contextualized_path(&self, db: &'db dyn Database, context_module: ModuleId<'db>) -> String {
404 let path = self.trait_id.contextualized_path(db, context_module);
405 if self.generic_args.is_empty() {
406 path
407 } else {
408 let args = self
409 .generic_args
410 .iter()
411 .map(|arg| arg.contextualized_path(db, context_module))
412 .format(", ");
413 format!("{path}::<{args}>")
414 }
415 }
416}
417
418impl<'db> ContextualizePath<'db> for GenericTypeId<'db> {
419 fn contextualized_path(&self, db: &'db dyn Database, context_module: ModuleId<'db>) -> String {
420 match self {
421 GenericTypeId::Struct(id) => id.contextualized_path(db, context_module),
422 GenericTypeId::Enum(id) => id.contextualized_path(db, context_module),
423 GenericTypeId::Extern(id) => id.contextualized_path(db, context_module),
424 }
425 }
426}
427
428impl<'db> ContextualizePath<'db> for crate::ConcreteTypeId<'db> {
429 fn contextualized_path(&self, db: &'db dyn Database, context_module: ModuleId<'db>) -> String {
430 let path = self.generic_type(db).contextualized_path(db, context_module);
431 let generic_args = self.generic_args(db);
432 if generic_args.is_empty() {
433 path
434 } else {
435 let args = generic_args
436 .iter()
437 .map(|arg| arg.contextualized_path(db, context_module))
438 .format(", ");
439 format!("{path}::<{args}>")
440 }
441 }
442}
443
444impl<'db> ContextualizePath<'db> for crate::TypeId<'db> {
445 fn contextualized_path(&self, db: &'db dyn Database, context_module: ModuleId<'db>) -> String {
446 self.long(db).contextualized_path(db, context_module)
447 }
448}
449
450impl<'db> ContextualizePath<'db> for crate::TypeLongId<'db> {
451 fn contextualized_path(&self, db: &'db dyn Database, context_module: ModuleId<'db>) -> String {
452 match self {
453 crate::TypeLongId::Concrete(concrete) => {
454 concrete.contextualized_path(db, context_module)
455 }
456 crate::TypeLongId::Tuple(inner_types) => {
457 let inner = inner_types.iter().map(|ty| ty.contextualized_path(db, context_module));
458 match inner.exactly_one() {
459 Ok(single) => format!("({single},)"),
460 Err(other) => format!("({})", other.format(", ")),
461 }
462 }
463 crate::TypeLongId::Snapshot(ty) => {
464 format!("@{}", ty.contextualized_path(db, context_module))
465 }
466 crate::TypeLongId::FixedSizeArray { type_id, size } => {
467 format!(
468 "[{}; {}]",
469 type_id.contextualized_path(db, context_module),
470 size.contextualized_path(db, context_module)
471 )
472 }
473 crate::TypeLongId::Coupon(function_id) => {
474 format!("{}::Coupon", function_id.contextualized_path(db, context_module))
475 }
476 crate::TypeLongId::ImplType(impl_type_id) => format!(
477 "{}::{}",
478 impl_type_id.impl_id().contextualized_path(db, context_module),
479 impl_type_id.ty().name(db).long(db)
480 ),
481 _ => self.format(db),
482 }
483 }
484}
485
486impl<'db> ContextualizePath<'db> for crate::GenericArgumentId<'db> {
487 fn contextualized_path(&self, db: &'db dyn Database, context_module: ModuleId<'db>) -> String {
488 match self {
489 crate::GenericArgumentId::Type(ty) => ty.contextualized_path(db, context_module),
490 crate::GenericArgumentId::Constant(value) => {
491 value.contextualized_path(db, context_module)
492 }
493 crate::GenericArgumentId::Impl(imp) => imp.contextualized_path(db, context_module),
494 crate::GenericArgumentId::NegImpl(_) => self.format(db),
495 }
496 }
497}
498
499impl<'db> ContextualizePath<'db> for crate::items::constant::ConstValueId<'db> {
500 fn contextualized_path(&self, db: &'db dyn Database, context_module: ModuleId<'db>) -> String {
501 self.long(db).contextualized_path(db, context_module)
502 }
503}
504
505impl<'db> ContextualizePath<'db> for crate::items::constant::ConstValue<'db> {
506 fn contextualized_path(&self, db: &'db dyn Database, context_module: ModuleId<'db>) -> String {
507 use crate::items::constant::ConstValue;
508 match self {
509 ConstValue::Int(value, _) => value.to_string(),
510 ConstValue::Enum(variant, payload) => {
511 let variant_path = variant.id.contextualized_path(db, context_module);
512 if matches!(payload.long(db), ConstValue::Struct(members, _) if members.is_empty())
514 {
515 variant_path
516 } else {
517 format!("{variant_path}({})", payload.contextualized_path(db, context_module))
518 }
519 }
520 ConstValue::NonZero(inner) => inner.contextualized_path(db, context_module),
521 ConstValue::ImplConstant(impl_constant_id) => format!(
522 "{}::{}",
523 impl_constant_id.impl_id().contextualized_path(db, context_module),
524 impl_constant_id.trait_constant_id().name(db).long(db)
525 ),
526 ConstValue::Generic(_)
527 | ConstValue::Struct(..)
528 | ConstValue::Var(..)
529 | ConstValue::Missing(_) => format!("{:?}", self.debug(db)),
530 }
531 }
532}
533
534impl<'db> ContextualizePath<'db> for crate::items::imp::ImplId<'db> {
535 fn contextualized_path(&self, db: &'db dyn Database, context_module: ModuleId<'db>) -> String {
536 self.long(db).contextualized_path(db, context_module)
537 }
538}
539
540impl<'db> ContextualizePath<'db> for crate::items::imp::ImplLongId<'db> {
541 fn contextualized_path(&self, db: &'db dyn Database, context_module: ModuleId<'db>) -> String {
542 use crate::items::imp::ImplLongId;
543 match self {
544 ImplLongId::Concrete(concrete_impl) => {
545 let long = concrete_impl.long(db);
546 let path = long.impl_def_id.contextualized_path(db, context_module);
547 if long.generic_args.is_empty() {
548 path
549 } else {
550 let args = long
551 .generic_args
552 .iter()
553 .map(|arg| arg.contextualized_path(db, context_module))
554 .format(", ");
555 format!("{path}::<{args}>")
556 }
557 }
558 ImplLongId::GenericParameter(param) => {
559 param.name(db).map(|name| name.to_string(db)).unwrap_or_else(|| self.format(db))
560 }
561 ImplLongId::SelfImpl(concrete_trait) => {
562 concrete_trait.contextualized_path(db, context_module)
563 }
564 ImplLongId::ImplImpl(impl_impl) => format!(
565 "{}::{}",
566 impl_impl.impl_id().contextualized_path(db, context_module),
567 impl_impl.trait_impl_id().name(db).long(db)
568 ),
569 ImplLongId::ImplVar(_) | ImplLongId::GeneratedImpl(_) => self.format(db),
571 }
572 }
573}
574
575impl<'db> ContextualizePath<'db> for GenericFunctionId<'db> {
576 fn contextualized_path(&self, db: &'db dyn Database, context_module: ModuleId<'db>) -> String {
577 match self {
578 GenericFunctionId::Free(id) => id.contextualized_path(db, context_module),
579 GenericFunctionId::Extern(id) => id.contextualized_path(db, context_module),
580 GenericFunctionId::Impl(id) => format!(
581 "{}::{}",
582 id.impl_id.contextualized_path(db, context_module),
583 id.function.name(db).long(db)
584 ),
585 }
586 }
587}
588
589impl<'db> ContextualizePath<'db> for crate::items::functions::ConcreteFunction<'db> {
590 fn contextualized_path(&self, db: &'db dyn Database, context_module: ModuleId<'db>) -> String {
591 let path = self.generic_function.contextualized_path(db, context_module);
592 if self.generic_args.is_empty() {
593 path
594 } else {
595 let args = self
596 .generic_args
597 .iter()
598 .map(|arg| arg.contextualized_path(db, context_module))
599 .format(", ");
600 format!("{path}::<{args}>")
601 }
602 }
603}
604
605impl<'db> ContextualizePath<'db> for crate::FunctionId<'db> {
606 fn contextualized_path(&self, db: &'db dyn Database, context_module: ModuleId<'db>) -> String {
607 self.long(db).contextualized_path(db, context_module)
608 }
609}
610
611impl<'db> ContextualizePath<'db> for crate::items::functions::FunctionLongId<'db> {
612 fn contextualized_path(&self, db: &'db dyn Database, context_module: ModuleId<'db>) -> String {
613 self.function.contextualized_path(db, context_module)
614 }
615}
616
617impl<'db> ContextualizePath<'db> for crate::items::functions::ConcreteFunctionWithBodyId<'db> {
618 fn contextualized_path(&self, db: &'db dyn Database, context_module: ModuleId<'db>) -> String {
619 match self.function_id(db) {
620 Ok(id) => id.contextualized_path(db, context_module),
621 Err(_) => self.full_path(db),
622 }
623 }
624}