cairo_lang_sierra/
debug_info.rs

1use std::collections::HashMap;
2use std::hash::Hash;
3
4use cairo_lang_utils::ordered_hash_map::OrderedHashMap;
5use itertools::Itertools;
6use serde::{Deserialize, Serialize};
7use smol_str::SmolStr;
8
9use crate::ids::{ConcreteLibfuncId, ConcreteTypeId, FunctionId};
10use crate::program::{GenericArg, Program, Statement};
11
12#[cfg(test)]
13#[path = "debug_info_test.rs"]
14mod test;
15
16/// Debug information for a Sierra program, to get readable names.
17#[derive(Clone, Debug, Eq, PartialEq, Default, Serialize, Deserialize)]
18pub struct DebugInfo {
19    #[serde(
20        serialize_with = "serialize_map::<ConcreteTypeId, _>",
21        deserialize_with = "deserialize_map::<ConcreteTypeId, _>"
22    )]
23    pub type_names: HashMap<ConcreteTypeId, SmolStr>,
24    #[serde(
25        serialize_with = "serialize_map::<ConcreteLibfuncId, _>",
26        deserialize_with = "deserialize_map::<ConcreteLibfuncId, _>"
27    )]
28    pub libfunc_names: HashMap<ConcreteLibfuncId, SmolStr>,
29    #[serde(
30        serialize_with = "serialize_map::<FunctionId, _>",
31        deserialize_with = "deserialize_map::<FunctionId, _>"
32    )]
33    pub user_func_names: HashMap<FunctionId, SmolStr>,
34    /// Non-crucial information about the program, for use by external libraries and tool.
35    ///
36    /// See [`Annotations`] type documentation for more information about this field.
37    #[serde(default, skip_serializing_if = "Annotations::is_empty")]
38    pub annotations: Annotations,
39    /// List of functions marked as executable.
40    #[serde(default, skip_serializing_if = "HashMap::is_empty")]
41    pub executables: HashMap<SmolStr, Vec<FunctionId>>,
42}
43
44/// Store for non-crucial information about the program, for use by external libraries and tool.
45///
46/// Keys represent tool namespaces, and values are tool-specific annotations themselves.
47/// Annotation values are JSON values, so they can be arbitrarily complex.
48///
49/// ## Namespaces
50///
51/// In order to avoid collisions between tools, namespaces should be URL-like, contain tool name.
52/// It is not required for namespace URLs to exist, but it is preferable nonetheless.
53///
54/// A single tool might want to use multiple namespaces, for example to group together annotations
55/// coming from different subcomponents of the tool. In such case, namespaces should use path-like
56/// notation (e.g. `example.com/sub-namespace`).
57///
58/// For future-proofing, it might be a good idea to version namespaces, e.g. `example.com/v1`.
59///
60/// ### Example well-formed namespaces
61///
62/// - `scarb.swmansion.com`
63/// - `scarb.swmansion.com/v1`
64/// - `scarb.swmansion.com/build-info/v1`
65pub type Annotations = OrderedHashMap<String, serde_json::Value>;
66
67impl DebugInfo {
68    /// Extracts the existing debug info from a program.
69    pub fn extract(program: &Program) -> Self {
70        Self {
71            type_names: program
72                .type_declarations
73                .iter()
74                .filter_map(|decl| {
75                    decl.id.debug_name.clone().map(|name| (ConcreteTypeId::new(decl.id.id), name))
76                })
77                .collect(),
78            libfunc_names: program
79                .libfunc_declarations
80                .iter()
81                .filter_map(|decl| {
82                    decl.id
83                        .debug_name
84                        .clone()
85                        .map(|name| (ConcreteLibfuncId::new(decl.id.id), name))
86                })
87                .collect(),
88            user_func_names: program
89                .funcs
90                .iter()
91                .filter_map(|func| {
92                    func.id.debug_name.clone().map(|name| (FunctionId::new(func.id.id), name))
93                })
94                .collect(),
95            annotations: Default::default(),
96            executables: Default::default(),
97        }
98    }
99
100    /// Populates a program with debug info.
101    pub fn populate(&self, program: &mut Program) {
102        for decl in &mut program.type_declarations {
103            self.try_replace_type_id(&mut decl.id);
104            self.try_replace_generic_arg_ids(&mut decl.long_id.generic_args);
105        }
106        for decl in &mut program.libfunc_declarations {
107            self.try_replace_libfunc_id(&mut decl.id);
108            self.try_replace_generic_arg_ids(&mut decl.long_id.generic_args);
109        }
110        for func in &mut program.funcs {
111            self.try_replace_function_id(&mut func.id);
112            for param in &mut func.params {
113                self.try_replace_type_id(&mut param.ty);
114            }
115            for id in &mut func.signature.param_types {
116                self.try_replace_type_id(id);
117            }
118            for id in &mut func.signature.ret_types {
119                self.try_replace_type_id(id);
120            }
121        }
122        for statement in &mut program.statements {
123            match statement {
124                Statement::Invocation(invocation) => {
125                    self.try_replace_libfunc_id(&mut invocation.libfunc_id)
126                }
127                Statement::Return(_) => {}
128            }
129        }
130    }
131
132    /// Replaces the debug names of the generic args if exists in the maps.
133    fn try_replace_generic_arg_ids(&self, generic_args: &mut Vec<GenericArg>) {
134        for generic_arg in generic_args {
135            match generic_arg {
136                GenericArg::Type(id) => self.try_replace_type_id(id),
137                GenericArg::Libfunc(id) => self.try_replace_libfunc_id(id),
138                GenericArg::UserFunc(id) => self.try_replace_function_id(id),
139                GenericArg::Value(_) | GenericArg::UserType(_) => {}
140            }
141        }
142    }
143
144    /// Replaces the debug name of an id if exists in the matching map.
145    fn try_replace_type_id(&self, id: &mut ConcreteTypeId) {
146        if let Some(name) = self.type_names.get(id).cloned() {
147            let _ = id.debug_name.insert(name);
148        }
149    }
150
151    /// Replaces the debug name of an id if exists in the matching map.
152    fn try_replace_libfunc_id(&self, id: &mut ConcreteLibfuncId) {
153        if let Some(name) = self.libfunc_names.get(id).cloned() {
154            let _ = id.debug_name.insert(name);
155        }
156    }
157
158    /// Replaces the debug name of an id if exists in the matching map.
159    fn try_replace_function_id(&self, id: &mut FunctionId) {
160        if let Some(name) = self.user_func_names.get(id).cloned() {
161            let _ = id.debug_name.insert(name);
162        }
163    }
164}
165
166/// Trait for handling serde for the ids as map keys.
167pub trait IdAsHashKey: Hash + Eq {
168    /// Gets the inner id.
169    fn get(&self) -> u64;
170    /// Returns a new id from the given value.
171    fn new(id: u64) -> Self;
172}
173
174impl IdAsHashKey for ConcreteTypeId {
175    fn get(&self) -> u64 {
176        self.id
177    }
178
179    fn new(id: u64) -> Self {
180        Self::new(id)
181    }
182}
183impl IdAsHashKey for ConcreteLibfuncId {
184    fn get(&self) -> u64 {
185        self.id
186    }
187
188    fn new(id: u64) -> Self {
189        Self::new(id)
190    }
191}
192impl IdAsHashKey for FunctionId {
193    fn get(&self) -> u64 {
194        self.id
195    }
196
197    fn new(id: u64) -> Self {
198        Self::new(id)
199    }
200}
201
202fn serialize_map<Id: IdAsHashKey, S: serde::Serializer>(
203    m: &HashMap<Id, SmolStr>,
204    serializer: S,
205) -> Result<S::Ok, S::Error> {
206    let v: Vec<_> = m.iter().map(|(id, name)| (id.get(), name)).sorted().collect();
207    v.serialize(serializer)
208}
209
210fn deserialize_map<'de, Id: IdAsHashKey, D: serde::Deserializer<'de>>(
211    deserializer: D,
212) -> Result<HashMap<Id, SmolStr>, D::Error> {
213    Ok(Vec::<(u64, SmolStr)>::deserialize(deserializer)?
214        .into_iter()
215        .map(|(id, name)| (Id::new(id), name))
216        .collect())
217}