1use alloc::{boxed::Box, sync::Arc};
2
3use miden_assembly_syntax::{
4 Report,
5 ast::types::{EnumRef, RecTypeRef, StructRef},
6};
7use miden_core::mast::MastNodeId;
8use miden_debug_types::{Location, Uri};
9use miden_utils_indexing::{Idx, IndexedVecError};
10
11use super::{
12 DebugErrorMessage, DebugInfo, DebugLoc, DebugLocIdx, DebugSourceNodeId, FunctionInfo,
13 FxHashMap, SourceNode, SourceNodeIdMarker,
14 types::{
15 DebugFileIdx, DebugFileInfo, DebugFunctionIdx, DebugStringIdx, DebugTypeIdx, DebugTypeInfo,
16 },
17};
18use crate::debug_info::{DebugFieldInfo, DebugPrimitiveType, DebugVariantInfo};
19
20pub type PackageDebugInfoBuilder = DebugInfoBuilder<MastNodeId, DebugSourceNodeId>;
28
29pub struct DebugInfoBuilder<Exec: Idx, Src: Idx> {
42 string_indices: FxHashMap<Arc<str>, DebugStringIdx>,
44 location_indices: FxHashMap<DebugLoc, DebugLocIdx>,
46 type_indices: FxHashMap<DebugTypeInfo, DebugTypeIdx>,
48 reserved_recursive_types: FxHashMap<RecTypeRef, DebugTypeIdx>,
54 debug_info: Box<DebugInfo<Exec, Src>>,
56}
57
58impl<Exec: Idx, Src: Idx> core::fmt::Debug for DebugInfoBuilder<Exec, Src> {
62 fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
63 f.debug_struct("DebugInfoBuilder")
64 .field("string_indices", &self.string_indices)
65 .field("debug_info", &self.debug_info)
66 .finish()
67 }
68}
69
70impl<Exec: Idx, Src: Idx> Default for DebugInfoBuilder<Exec, Src> {
71 fn default() -> Self {
72 Self {
73 string_indices: Default::default(),
74 location_indices: Default::default(),
75 type_indices: Default::default(),
76 reserved_recursive_types: Default::default(),
77 debug_info: Default::default(),
78 }
79 }
80}
81
82impl<Exec: Idx + Clone, Src: Idx + Clone> Clone for DebugInfoBuilder<Exec, Src> {
83 fn clone(&self) -> Self {
84 Self {
85 string_indices: self.string_indices.clone(),
86 location_indices: self.location_indices.clone(),
87 type_indices: self.type_indices.clone(),
88 reserved_recursive_types: self.reserved_recursive_types.clone(),
89 debug_info: self.debug_info.clone(),
90 }
91 }
92}
93
94impl<Exec: Idx, Src: Idx> core::ops::Index<DebugStringIdx> for DebugInfoBuilder<Exec, Src> {
98 type Output = Arc<str>;
99
100 fn index(&self, index: DebugStringIdx) -> &Self::Output {
101 &self.debug_info[index]
102 }
103}
104
105impl<Exec: Idx, Src: Idx> core::ops::Index<DebugFileIdx> for DebugInfoBuilder<Exec, Src> {
106 type Output = DebugFileInfo;
107
108 fn index(&self, index: DebugFileIdx) -> &Self::Output {
109 &self.debug_info[index]
110 }
111}
112
113impl<Exec: Idx, Src: Idx> core::ops::Index<DebugFunctionIdx> for DebugInfoBuilder<Exec, Src> {
114 type Output = FunctionInfo<Src>;
115
116 fn index(&self, index: DebugFunctionIdx) -> &Self::Output {
117 &self.debug_info[index]
118 }
119}
120
121impl<Exec: Idx, Src: Idx> core::ops::Index<DebugLocIdx> for DebugInfoBuilder<Exec, Src> {
122 type Output = DebugLoc;
123
124 fn index(&self, index: DebugLocIdx) -> &Self::Output {
125 &self.debug_info[index]
126 }
127}
128
129impl<Exec: Idx, Src: Idx> core::ops::Index<DebugTypeIdx> for DebugInfoBuilder<Exec, Src> {
130 type Output = DebugTypeInfo;
131
132 fn index(&self, index: DebugTypeIdx) -> &Self::Output {
133 &self.debug_info[index]
134 }
135}
136
137impl<Exec: Idx, Src: SourceNodeIdMarker> core::ops::Index<Src> for DebugInfoBuilder<Exec, Src> {
138 type Output = SourceNode<Exec, Src>;
139
140 fn index(&self, index: Src) -> &Self::Output {
141 &self.debug_info[index]
142 }
143}
144
145impl<Exec: Idx, Src: SourceNodeIdMarker> core::ops::IndexMut<Src> for DebugInfoBuilder<Exec, Src> {
146 fn index_mut(&mut self, index: Src) -> &mut Self::Output {
147 &mut self.debug_info.nodes[index]
148 }
149}
150
151impl<Exec: Idx, Src: Idx> From<Box<DebugInfo<Exec, Src>>> for DebugInfoBuilder<Exec, Src> {
155 fn from(debug_info: Box<DebugInfo<Exec, Src>>) -> Self {
156 use hashbrown::hash_map::Entry;
157
158 let mut string_indices = FxHashMap::default();
159 for (i, string) in debug_info.strings().iter().enumerate() {
160 if let Entry::Vacant(entry) = string_indices.entry(string.clone()) {
161 let idx = DebugStringIdx::from(i as u32);
162 entry.insert(idx);
163 }
164 }
165 let mut location_indices = FxHashMap::default();
166 for (i, loc) in debug_info.locations().iter().enumerate() {
167 if let Entry::Vacant(entry) = location_indices.entry(*loc) {
168 let idx = DebugLocIdx::from(i as u32);
169 entry.insert(idx);
170 }
171 }
172 let mut type_indices = FxHashMap::default();
173 for (i, ty) in debug_info.types().iter().enumerate() {
174 if let Entry::Vacant(entry) = type_indices.entry(ty.clone()) {
175 let idx = DebugTypeIdx::from(i as u32);
176 entry.insert(idx);
177 }
178 }
179 Self {
180 string_indices,
181 location_indices,
182 type_indices,
183 reserved_recursive_types: Default::default(),
184 debug_info,
185 }
186 }
187}
188
189impl<Exec: Idx, Src: Idx> DebugInfoBuilder<Exec, Src> {
190 #[inline]
194 pub fn build(self) -> Box<DebugInfo<Exec, Src>> {
195 self.debug_info
196 }
197}
198
199impl<Exec: Idx, Src: Idx> DebugInfoBuilder<Exec, Src> {
203 pub fn debug_info(&self) -> &DebugInfo<Exec, Src> {
205 &self.debug_info
206 }
207
208 pub fn debug_info_mut(&mut self) -> &mut DebugInfo<Exec, Src> {
210 &mut self.debug_info
211 }
212}
213
214impl<Exec: Idx, Src: Idx> DebugInfoBuilder<Exec, Src> {
218 pub fn get_string(&self, idx: DebugStringIdx) -> Option<Arc<str>> {
220 self.debug_info.strings.get(idx).cloned()
221 }
222
223 pub fn get_string_index(&self, s: &str) -> Option<DebugStringIdx> {
225 self.string_indices.get(s).copied()
226 }
227
228 pub fn add_string(&mut self, s: impl Into<Arc<str>>) -> DebugStringIdx {
232 let s = s.into();
233 if let Some(exists) = self.string_indices.get(&s).copied() {
234 return exists;
235 }
236 let idx = self.debug_info.strings.push(s.clone()).expect("too many strings");
237 self.string_indices.insert(s, idx);
238 idx
239 }
240}
241
242impl<Exec: Idx, Src: Idx> DebugInfoBuilder<Exec, Src> {
246 pub fn get_file_index_by_uri(&self, uri: &Uri) -> Option<DebugFileIdx> {
249 self.debug_info.get_file_index_by_uri(uri)
250 }
251
252 pub fn get_file_index_by_path_index(&self, path_idx: DebugStringIdx) -> Option<DebugFileIdx> {
253 self.debug_info
254 .files
255 .iter()
256 .position(|file| file.path_idx == path_idx)
257 .map(|pos| DebugFileIdx::from(pos as u32))
258 }
259
260 pub fn add_file(&mut self, uri: Uri, checksum: Option<[u8; 32]>) -> DebugFileIdx {
265 let path_idx = self.add_string(uri);
266 self.add_file_info(
267 DebugFileInfo::new(path_idx)
268 .with_checksum(checksum.unwrap_or(DebugFileInfo::EMPTY_CHECKSUM)),
269 )
270 }
271
272 pub fn add_file_info(&mut self, file: DebugFileInfo) -> DebugFileIdx {
274 assert!(
275 self.debug_info.strings.get(file.path_idx).is_some(),
276 "invalid path string index"
277 );
278 if let Some(idx) = self.debug_info.files.iter().position(|existing| existing == &file) {
279 return DebugFileIdx::from(idx as u32);
280 }
281 self.debug_info.files.push(file).expect("too many files")
282 }
283}
284
285impl<Exec: Idx, Src: Idx> DebugInfoBuilder<Exec, Src> {
289 pub fn add_location(&mut self, loc: Location) -> DebugLocIdx {
291 let path_idx = self.add_string(loc.uri().clone());
292 let file_idx = self
293 .get_file_index_by_path_index(path_idx)
294 .unwrap_or_else(|| self.add_file_info(DebugFileInfo::new(path_idx)));
295 self.add_location_info(DebugLoc { file_idx, start: loc.start, end: loc.end })
296 }
297
298 pub fn add_location_info(&mut self, loc: DebugLoc) -> DebugLocIdx {
303 use hashbrown::hash_map::Entry;
304
305 assert!(self.debug_info.files.get(loc.file_idx).is_some(), "invalid source file index");
306
307 match self.location_indices.entry(loc) {
308 Entry::Occupied(entry) => *entry.get(),
309 Entry::Vacant(entry) => {
310 let index = self.debug_info.locations.push(loc).expect("too many locations");
311 entry.insert(index);
312 index
313 },
314 }
315 }
316}
317
318impl<Exec: Idx, Src: Idx> DebugInfoBuilder<Exec, Src> {
322 pub fn add_type(&mut self, ty: DebugTypeInfo) -> DebugTypeIdx {
324 use hashbrown::hash_map::Entry;
325 match self.type_indices.entry(ty.clone()) {
326 Entry::Occupied(entry) => *entry.get(),
327 Entry::Vacant(entry) => {
328 let index = self.debug_info.types.push(ty).expect("too many types");
329 entry.insert(index);
330 index
331 },
332 }
333 }
334
335 fn reserve_type(&mut self) -> DebugTypeIdx {
341 self.debug_info.types.push(DebugTypeInfo::Unknown).expect("too many types")
342 }
343
344 fn finish_reserved_type(&mut self, index: DebugTypeIdx, ty: DebugTypeInfo) {
346 self.debug_info.types[index] = ty.clone();
347 self.type_indices.entry(ty).or_insert(index);
348 }
349
350 #[cfg(test)]
352 pub(crate) fn replace_type_for_test(&mut self, index: DebugTypeIdx, ty: DebugTypeInfo) {
353 self.debug_info.types[index] = ty;
354 }
355
356 pub(crate) fn push_type(&mut self, ty: DebugTypeInfo) -> DebugTypeIdx {
361 let index = self.debug_info.types.push(ty.clone()).expect("too many types");
362 self.type_indices.entry(ty).or_insert(index);
363 index
364 }
365}
366
367impl<Exec: Idx, Src: Idx> DebugInfoBuilder<Exec, Src> {
371 pub fn get_function_index_by_path(
376 &self,
377 path: &miden_assembly_syntax::Path,
378 ) -> Option<DebugFunctionIdx> {
379 let path = path.as_str();
380 let mut name_match = None;
381 for (pos, f) in self.debug_info.functions.iter().enumerate() {
382 if let Some(linkage_name_idx) = f.linkage_name_idx.into_option()
383 && self.debug_info[linkage_name_idx].as_ref() == path
384 {
385 return Some(DebugFunctionIdx::from(pos as u32));
386 }
387 if name_match.is_none() && self.debug_info[f.name_idx].as_ref() == path {
388 name_match = Some(pos);
389 }
390 }
391 name_match.map(|pos| DebugFunctionIdx::from(pos as u32))
392 }
393
394 pub fn add_function(&mut self, func: FunctionInfo<Src>) -> DebugFunctionIdx {
396 self.debug_info.functions.push(func).expect("too many functions")
397 }
398
399 pub fn set_function_source_node(&mut self, index: DebugFunctionIdx, node: Src) {
401 self.debug_info.functions[index].source_node = Some(node).into();
402 }
403}
404
405impl<Exec: Idx, Src: Idx> DebugInfoBuilder<Exec, Src> {
409 pub fn add_error_message(&mut self, err_code: u64, message: Arc<str>) -> bool {
413 if !self.debug_info.error_messages.iter().any(|msg| msg.err_code == err_code) {
414 let message = self.add_string(message);
415 self.debug_info.error_messages.push(DebugErrorMessage::new(err_code, message));
416 true
417 } else {
418 false
419 }
420 }
421
422 pub fn add_error_message_with_index(&mut self, err_code: u64, message: DebugStringIdx) {
428 assert!(
429 self.debug_info.get_string(message).is_some(),
430 "invalid string index for message"
431 );
432 if !self.debug_info.error_messages.iter().any(|msg| msg.err_code == err_code) {
433 self.debug_info.error_messages.push(DebugErrorMessage::new(err_code, message));
434 }
435 }
436}
437
438impl<Exec: Idx, Src: Idx> DebugInfoBuilder<Exec, Src> {
442 pub fn add_node(&mut self, mut node: SourceNode<Exec, Src>) -> Result<Src, IndexedVecError> {
444 assert!(node.op_end >= node.op_start);
445 assert!(node.children.iter().copied().all(|n| self.debug_info.source_node(n).is_some()));
446 node.asm_ops.sort_unstable_by_key(|row| row.op_idx);
447 self.debug_info.nodes.push(node)
448 }
449
450 pub fn roots(&self) -> &[Src] {
452 self.debug_info.roots()
453 }
454
455 pub fn add_root(&mut self, node: Src) {
457 assert!(self.debug_info.source_node(node).is_some());
458 if !self.debug_info.roots.contains(&node) {
459 self.debug_info.roots.push(node);
460 }
461 }
462}
463
464impl<Exec: Idx, Src: Idx> DebugInfoBuilder<Exec, Src> {
465 fn struct_debug_type(
475 &mut self,
476 declared_name: Option<DebugStringIdx>,
477 declared_ty: Option<&miden_assembly_syntax::ast::TypeExpr>,
478 struct_ty: &miden_assembly_syntax::ast::types::StructType,
479 ) -> Result<DebugTypeInfo, Report> {
480 use miden_assembly_syntax::ast::TypeExpr;
481
482 let declared_field_tys = declared_ty.and_then(|t| match t {
483 TypeExpr::Struct(t) => Some(&t.fields),
484 _ => None,
485 });
486 let mut fields = vec![];
487 for (i, field) in struct_ty.fields().iter().enumerate() {
488 let decl = declared_field_tys.and_then(|fields| fields.get(i));
489 let field_name =
490 decl.map(|decl| decl.name.clone().into_inner()).or_else(|| field.name.clone());
491 let declared_ty = decl.map(|decl| &decl.ty);
492 let field_type_name = declared_type_name(declared_ty);
493 let field_type_name = field_type_name.map(|name| self.add_string(name));
494 let type_idx = self.register_debug_type(field_type_name, declared_ty, &field.ty)?;
495 let name_idx = self.add_string(field_name.unwrap_or_else(|| format!("{i}").into()));
496 fields.push(DebugFieldInfo { name_idx, type_idx, offset: field.offset });
497 }
498 let struct_name =
499 declared_name.or_else(|| struct_ty.name().map(|name| self.add_string(name)));
500 let name_idx = struct_name.unwrap_or_else(|| self.add_string("<anon>"));
501 let size = u32::try_from(struct_ty.size()).map_err(|_| {
502 if let Some(declared_name) = struct_name.as_ref() {
503 Report::msg(format!(
504 "invalid struct type '{}': struct is too large",
505 self.get_string(*declared_name).unwrap()
506 ))
507 } else {
508 Report::msg("invalid struct type: struct is too large")
509 }
510 })?;
511 Ok(DebugTypeInfo::Struct { name_idx, size, fields })
512 }
513
514 fn enum_debug_type(
516 &mut self,
517 enum_ty: &miden_assembly_syntax::ast::types::EnumType,
518 ) -> Result<DebugTypeInfo, Report> {
519 let discrim_ty = self.register_debug_type(None, None, enum_ty.discriminant())?;
520 let name_idx = self.add_string(enum_ty.name().clone());
521 let size = u32::try_from(enum_ty.size_in_bytes()).map_err(|_| {
522 Report::msg(format!("invalid enum type '{}': enum is too large", enum_ty.name()))
523 })?;
524 let variants = enum_ty
525 .variant_offsets()
526 .zip(enum_ty.discriminant_values())
527 .map(|((payload_offset, variant), discriminant)| {
528 let name_idx = self.add_string(variant.name.clone());
529 let type_idx = variant
530 .value
531 .as_ref()
532 .map(|ty| self.register_debug_type(None, None, ty))
533 .transpose()?;
534 let payload_offset = variant.value.as_ref().map(|_| payload_offset);
535 Ok(DebugVariantInfo {
536 name_idx,
537 type_idx,
538 payload_offset,
539 discriminant,
540 })
541 })
542 .collect::<Result<_, Report>>()?;
543 Ok(DebugTypeInfo::Enum {
544 name_idx,
545 size,
546 discriminant_type_idx: discrim_ty,
547 variants,
548 })
549 }
550
551 pub fn register_debug_type(
552 &mut self,
553 declared_name: Option<DebugStringIdx>,
554 declared_ty: Option<&miden_assembly_syntax::ast::TypeExpr>,
555 ty: &miden_assembly_syntax::ast::types::Type,
556 ) -> Result<DebugTypeIdx, Report> {
557 use miden_assembly_syntax::ast::{
558 TypeExpr,
559 types::{StructType, Type},
560 };
561 Ok(match ty {
562 Type::I1 => self.add_type(DebugTypeInfo::Primitive(DebugPrimitiveType::Bool)),
563 Type::I8 => self.add_type(DebugTypeInfo::Primitive(DebugPrimitiveType::I8)),
564 Type::U8 => self.add_type(DebugTypeInfo::Primitive(DebugPrimitiveType::U8)),
565 Type::I16 => self.add_type(DebugTypeInfo::Primitive(DebugPrimitiveType::I16)),
566 Type::U16 => self.add_type(DebugTypeInfo::Primitive(DebugPrimitiveType::U16)),
567 Type::I32 => self.add_type(DebugTypeInfo::Primitive(DebugPrimitiveType::I32)),
568 Type::U32 => self.add_type(DebugTypeInfo::Primitive(DebugPrimitiveType::U32)),
569 Type::I64 => self.add_type(DebugTypeInfo::Primitive(DebugPrimitiveType::I64)),
570 Type::U64 => self.add_type(DebugTypeInfo::Primitive(DebugPrimitiveType::U64)),
571 Type::I128 => self.add_type(DebugTypeInfo::Primitive(DebugPrimitiveType::I128)),
572 Type::U128 => self.add_type(DebugTypeInfo::Primitive(DebugPrimitiveType::U128)),
573 Type::Felt => self.add_type(DebugTypeInfo::Primitive(DebugPrimitiveType::Felt)),
574 Type::F64 => self.add_type(DebugTypeInfo::Primitive(DebugPrimitiveType::F64)),
575 Type::U256 => self.add_type(DebugTypeInfo::Primitive(DebugPrimitiveType::U256)),
576 Type::Unknown => self.add_type(DebugTypeInfo::Unknown),
577 Type::Never => self.add_type(DebugTypeInfo::Primitive(DebugPrimitiveType::Void)),
578 Type::Variadic => self.add_type(DebugTypeInfo::Variadic),
579 Type::Ptr(ptr) => {
580 let pointee_name = declared_ty.and_then(|t| match t {
581 TypeExpr::Ptr(p) => match p.pointee.as_ref() {
582 TypeExpr::Ref(p) => Some(Arc::from(p.inner().as_str())),
583 _ => None,
584 },
585 _ => None,
586 });
587 let pointee_name = pointee_name.map(|name| self.add_string(name));
588 let pointee_decl = declared_ty.and_then(|t| match t {
589 TypeExpr::Ptr(p) => Some(p.pointee.as_ref()),
590 _ => None,
591 });
592 let pointee_type_idx =
593 self.register_debug_type(pointee_name, pointee_decl, ptr.pointee())?;
594 self.add_type(DebugTypeInfo::Pointer { pointee_type_idx })
595 },
596 Type::Array(array) => {
597 let element_name = declared_ty.and_then(|t| match t {
598 TypeExpr::Array(array) => match array.elem.as_ref() {
599 TypeExpr::Ref(t) => Some(Arc::from(t.inner().as_str())),
600 _ => None,
601 },
602 _ => None,
603 });
604 let element_name = element_name.map(|name| self.add_string(name));
605 let element_decl = declared_ty.and_then(|t| match t {
606 TypeExpr::Array(p) => Some(p.elem.as_ref()),
607 _ => None,
608 });
609 let element_type_idx =
610 self.register_debug_type(element_name, element_decl, array.element_type())?;
611 let count = u32::try_from(array.len())
612 .map_err(|_| Report::msg("array type is too large"))?;
613 self.add_type(DebugTypeInfo::Array { element_type_idx, count: Some(count) })
614 },
615 Type::List(element_ty) => {
616 let element_type_idx = self.register_debug_type(None, None, element_ty)?;
624 self.add_type(DebugTypeInfo::Array { element_type_idx, count: None })
625 },
626 Type::Struct(StructRef::Rec(rec)) | Type::Enum(EnumRef::Rec(rec)) => {
630 if let Some(reserved) = self.reserved_recursive_types.get(rec) {
631 return Ok(*reserved);
632 }
633
634 let reserved = self.reserve_type();
635 self.reserved_recursive_types.insert(rec.clone(), reserved);
636
637 let body = match ty {
638 Type::Struct(ty) => {
639 self.struct_debug_type(declared_name, declared_ty, &ty.get())
640 },
641 Type::Enum(ty) => self.enum_debug_type(&ty.get()),
642 _ => unreachable!("matched a recursive struct or enum above"),
643 };
644
645 self.reserved_recursive_types.remove(rec);
646 self.finish_reserved_type(reserved, body?);
649 reserved
650 },
651 Type::Struct(struct_ty) => {
652 let info = self.struct_debug_type(declared_name, declared_ty, &struct_ty.get())?;
653 self.add_type(info)
654 },
655 Type::Enum(enum_ty) => {
656 let info = self.enum_debug_type(&enum_ty.get())?;
657 self.add_type(info)
658 },
659 Type::Function(fty) => {
660 let return_type_index = match fty.results() {
661 [] => self.add_type(DebugTypeInfo::Primitive(DebugPrimitiveType::Void)),
662 [ty] => self.register_debug_type(None, None, ty)?,
663 types => {
664 let ty = StructType::new(types.iter().cloned());
665 let size = u32::try_from(ty.size()).map_err(|_| {
666 if let Some(declared_name) = declared_name.as_ref() {
667 Report::msg(format!(
668 "invalid signature for '{declared_name}': return type is too big"
669 ))
670 } else {
671 Report::msg("invalid signature: return type is too big")
672 }
673 })?;
674 let mut fields = vec![];
675 for (i, field) in ty.fields().iter().enumerate() {
676 let name_idx = self.add_string(format!("{i}"));
677 let type_idx = self.register_debug_type(None, None, &field.ty)?;
678 fields.push(DebugFieldInfo {
679 name_idx,
680 type_idx,
681 offset: field.offset,
682 });
683 }
684 let name_idx = self.add_string("<anon>");
685 self.add_type(DebugTypeInfo::Struct { name_idx, size, fields })
686 },
687 };
688 let mut param_type_indices = vec![];
689 for param in fty.params() {
690 param_type_indices.push(self.register_debug_type(None, None, param)?);
691 }
692 self.add_type(DebugTypeInfo::Function {
693 return_type_idx: Some(return_type_index),
694 param_type_indices,
695 })
696 },
697 })
698 }
699}
700
701fn declared_type_name(
702 declared_ty: Option<&miden_assembly_syntax::ast::TypeExpr>,
703) -> Option<Arc<str>> {
704 use miden_assembly_syntax::ast::TypeExpr;
705 match declared_ty? {
706 TypeExpr::Ref(path) => Some(Arc::from(path.inner().as_str())),
707 TypeExpr::Struct(ty) => ty.name.as_ref().map(|name| name.clone().into_inner()),
708 TypeExpr::Primitive(_) | TypeExpr::Ptr(_) | TypeExpr::Array(_) => None,
709 }
710}
711
712#[cfg(test)]
713mod tests {
714 use alloc::sync::Arc;
715
716 use miden_assembly_syntax::{
717 Path,
718 ast::types::{CallConv, EnumType, FunctionType, StructType, Type, Variant},
719 };
720 use miden_core::Word;
721 use miden_debug_types::{ColumnNumber, LineNumber, Uri};
722 use miden_utils_indexing::Idx;
723
724 use super::*;
725
726 #[test]
727 fn registers_c_like_enum_debug_type() {
728 let mut builder = PackageDebugInfoBuilder::default();
729 let enum_ty = EnumType::new(
730 Arc::from("Status"),
731 Type::U16,
732 [
733 Variant::c_like(Arc::from("Ok"), Some(200)),
734 Variant::c_like(Arc::from("NotFound"), Some(404)),
735 ],
736 )
737 .unwrap();
738 let ty = Type::from(Arc::new(enum_ty));
739
740 let type_idx = builder.register_debug_type(None, None, &ty).unwrap();
741
742 let DebugTypeInfo::Enum {
743 name_idx,
744 size,
745 discriminant_type_idx,
746 variants,
747 } = &builder[type_idx]
748 else {
749 panic!("expected enum debug type");
750 };
751 assert_eq!(builder.get_string(*name_idx).as_deref(), Some("Status"));
752 assert_eq!(*size, 2);
753 assert_eq!(
754 &builder[*discriminant_type_idx],
755 &DebugTypeInfo::Primitive(DebugPrimitiveType::U16)
756 );
757 assert_eq!(variants.len(), 2);
758 assert_eq!(builder.get_string(variants[0].name_idx).as_deref(), Some("Ok"));
759 assert_eq!(variants[0].type_idx, None);
760 assert_eq!(variants[0].payload_offset, None);
761 assert_eq!(variants[0].discriminant, 200);
762 assert_eq!(builder.get_string(variants[1].name_idx).as_deref(), Some("NotFound"));
763 assert_eq!(variants[1].type_idx, None);
764 assert_eq!(variants[1].payload_offset, None);
765 assert_eq!(variants[1].discriminant, 404);
766 }
767
768 #[test]
769 fn registers_payload_enum_debug_type() {
770 let mut builder = PackageDebugInfoBuilder::default();
771 let enum_ty = EnumType::new(
772 Arc::from("OptionU32"),
773 Type::U8,
774 [
775 Variant::c_like(Arc::from("None"), Some(0)),
776 Variant::new(Arc::from("Some"), Type::U32, Some(1)),
777 ],
778 )
779 .unwrap();
780 let ty = Type::from(Arc::new(enum_ty));
781
782 let type_idx = builder.register_debug_type(None, None, &ty).unwrap();
783
784 let DebugTypeInfo::Enum { variants, .. } = &builder[type_idx] else {
785 panic!("expected enum debug type");
786 };
787 assert_eq!(variants.len(), 2);
788 assert_eq!(variants[0].type_idx, None);
789 let payload_type_idx = variants[1].type_idx.expect("Some variant should have payload");
790 assert_eq!(&builder[payload_type_idx], &DebugTypeInfo::Primitive(DebugPrimitiveType::U32));
791 assert_eq!(variants[1].payload_offset, Some(4));
792 assert_eq!(variants[1].discriminant, 1);
793 }
794
795 #[test]
796 fn function_debug_types_preserve_resolved_struct_metadata() {
797 let felt_wrapper = Type::from(Arc::new(StructType::named(
798 "felt-wrapper".into(),
799 [(Arc::from("inner"), Type::Felt)],
800 )));
801 let account_id = Type::from(Arc::new(StructType::named(
802 "account-id".into(),
803 [(Arc::from("prefix"), felt_wrapper.clone()), (Arc::from("suffix"), felt_wrapper)],
804 )));
805 let function = Type::Function(Arc::new(FunctionType::new(
806 CallConv::ComponentModel,
807 [account_id.clone()],
808 [account_id],
809 )));
810 let mut builder = PackageDebugInfoBuilder::default();
811
812 let function_name = builder.add_string("take-account-id");
813 let function_idx = builder
814 .register_debug_type(Some(function_name), None, &function)
815 .expect("function type should register");
816
817 let (return_type_idx, param_type_idx) = match &builder[function_idx] {
818 DebugTypeInfo::Function {
819 return_type_idx: Some(return_type_idx),
820 param_type_indices,
821 } => {
822 assert_eq!(param_type_indices.len(), 1);
823 (*return_type_idx, param_type_indices[0])
824 },
825 other => panic!("expected function debug type, got {other:?}"),
826 };
827
828 assert_struct_debug_type(
829 &builder,
830 param_type_idx,
831 "account-id",
832 &[("prefix", "felt-wrapper"), ("suffix", "felt-wrapper")],
833 );
834 assert_struct_debug_type(
835 &builder,
836 return_type_idx,
837 "account-id",
838 &[("prefix", "felt-wrapper"), ("suffix", "felt-wrapper")],
839 );
840 }
841
842 fn assert_struct_debug_type(
843 builder: &PackageDebugInfoBuilder,
844 type_idx: DebugTypeIdx,
845 expected_name: &str,
846 expected_fields: &[(&str, &str)],
847 ) {
848 let DebugTypeInfo::Struct { name_idx, fields, .. } = &builder[type_idx] else {
849 panic!("expected struct debug type");
850 };
851
852 assert_eq!(builder[*name_idx].as_ref(), expected_name);
853 assert_eq!(fields.len(), expected_fields.len());
854 for (field, (expected_name, expected_type_name)) in fields.iter().zip(expected_fields) {
855 assert_eq!(builder[field.name_idx].as_ref(), *expected_name);
856
857 let DebugTypeInfo::Struct { name_idx, .. } = &builder[field.type_idx] else {
858 panic!("expected struct field type");
859 };
860 assert_eq!(builder[*name_idx].as_ref(), *expected_type_name);
861 }
862 }
863
864 #[test]
865 fn test_debug_info_string_dedup() {
866 let mut builder = PackageDebugInfoBuilder::default();
867
868 let idx1 = builder.add_string(Arc::from("test.rs"));
869 let idx2 = builder.add_string(Arc::from("main.rs"));
870 let idx3 = builder.add_string(Arc::from("test.rs")); assert_eq!(idx1.to_usize(), 0);
873 assert_eq!(idx2.to_usize(), 1);
874 assert_eq!(idx3.to_usize(), 0); assert_eq!(builder.string_indices.len(), 2);
876 assert_eq!(builder.debug_info.strings.len(), 2);
877 }
878
879 fn add_test_function(
880 builder: &mut PackageDebugInfoBuilder,
881 name: &str,
882 linkage_name: Option<&str>,
883 ) -> DebugFunctionIdx {
884 let file_idx = builder.add_file(Uri::new("test.masm"), None);
885 let line = LineNumber::new(1).unwrap();
886 let column = ColumnNumber::new(1).unwrap();
887 let name_idx = builder.add_string(name);
888 let func = FunctionInfo::new(None, name_idx, file_idx, line, column, Word::default());
889 let func = match linkage_name {
890 Some(linkage_name) => {
891 let linkage_name_idx = builder.add_string(linkage_name);
892 func.with_linkage_name(linkage_name_idx)
893 },
894 None => func,
895 };
896 builder.add_function(func)
897 }
898
899 #[test]
900 fn function_path_lookup_no_match() {
901 let mut builder = PackageDebugInfoBuilder::default();
902 add_test_function(&mut builder, "::module::plain", None);
903 add_test_function(&mut builder, "duplicate", Some("::module::linked"));
904
905 assert_eq!(builder.get_function_index_by_path(Path::new("::module::missing")), None);
906 }
907
908 #[test]
909 fn function_path_lookup_matches_name() {
910 let mut builder = PackageDebugInfoBuilder::default();
911 let plain_function_idx = add_test_function(&mut builder, "::module::plain", None);
912
913 assert_eq!(
914 builder.get_function_index_by_path(Path::new("::module::plain")),
915 Some(plain_function_idx),
916 );
917 }
918
919 #[test]
920 fn function_path_lookup_matches_linkage_name() {
921 let mut builder = PackageDebugInfoBuilder::default();
922 let linked_function_idx =
923 add_test_function(&mut builder, "duplicate", Some("::module::linked"));
924
925 assert_eq!(
926 builder.get_function_index_by_path(Path::new("::module::linked")),
927 Some(linked_function_idx),
928 );
929 }
930
931 #[test]
932 fn function_path_lookup_matches_source_name_of_linked_function() {
933 let mut builder = PackageDebugInfoBuilder::default();
934 let linked_function_idx =
935 add_test_function(&mut builder, "::module::source", Some("::module::linked"));
936
937 assert_eq!(
938 builder.get_function_index_by_path(Path::new("::module::source")),
939 Some(linked_function_idx),
940 );
941 }
942
943 #[test]
944 fn function_path_lookup_prefers_linkage_name() {
945 let mut builder = PackageDebugInfoBuilder::default();
946 add_test_function(&mut builder, "::module::shared", None);
949 let linked_function_idx =
950 add_test_function(&mut builder, "duplicate", Some("::module::shared"));
951
952 assert_eq!(
953 builder.get_function_index_by_path(Path::new("::module::shared")),
954 Some(linked_function_idx),
955 );
956 }
957}