foundry_cheatcodes/inspector/
analysis.rs1use foundry_common::fmt::{StructDefinitions, TypeDefMap};
4use solar::sema::{self, Compiler, Gcx, hir};
5use std::sync::{Arc, OnceLock};
6use thiserror::Error;
7
8#[derive(Debug, Clone, PartialEq, Eq, Error)]
10pub enum AnalysisError {
11 #[error("unable to resolve struct definitions")]
13 StructDefinitionsResolutionFailed,
14}
15
16#[derive(Clone)]
36pub struct CheatcodeAnalysis {
37 pub compiler: Arc<Compiler>,
39
40 struct_defs: OnceLock<Result<StructDefinitions, AnalysisError>>,
43}
44
45impl std::fmt::Debug for CheatcodeAnalysis {
46 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
47 f.debug_struct("CheatcodeAnalysis")
48 .field("compiler", &"<compiler>")
49 .field("struct_defs", &self.struct_defs)
50 .finish()
51 }
52}
53
54impl CheatcodeAnalysis {
55 pub fn new(compiler: Arc<solar::sema::Compiler>) -> Self {
56 Self { compiler, struct_defs: OnceLock::new() }
57 }
58
59 pub fn struct_defs(&self) -> Result<&StructDefinitions, &AnalysisError> {
61 self.struct_defs
62 .get_or_init(|| {
63 self.compiler.enter(|compiler| {
64 let gcx = compiler.gcx();
65
66 StructDefinitionResolver::new(gcx).process()
67 })
68 })
69 .as_ref()
70 }
71}
72
73struct StructDefinitionResolver<'gcx> {
77 gcx: Gcx<'gcx>,
78 struct_defs: TypeDefMap,
79}
80
81impl<'gcx> StructDefinitionResolver<'gcx> {
82 pub fn new(gcx: Gcx<'gcx>) -> Self {
84 Self { gcx, struct_defs: TypeDefMap::new() }
85 }
86
87 pub fn process(mut self) -> Result<StructDefinitions, AnalysisError> {
89 for id in self.hir().strukt_ids() {
90 self.resolve_struct_definition(id)?;
91 }
92 Ok(self.struct_defs.into())
93 }
94
95 #[inline]
96 fn hir(&self) -> &'gcx hir::Hir<'gcx> {
97 &self.gcx.hir
98 }
99
100 fn resolve_struct_definition(&mut self, id: hir::StructId) -> Result<(), AnalysisError> {
102 let qualified_name = self.get_fully_qualified_name(id);
103 if self.struct_defs.contains_key(&qualified_name) {
104 return Ok(());
105 }
106
107 let hir = self.hir();
108 let strukt = hir.strukt(id);
109 let mut fields = Vec::with_capacity(strukt.fields.len());
110
111 for &field_id in strukt.fields {
112 let var = hir.variable(field_id);
113 let name =
114 var.name.ok_or(AnalysisError::StructDefinitionsResolutionFailed)?.to_string();
115 if let Some(ty_str) = self.ty_to_string(self.gcx.type_of_hir_ty(&var.ty)) {
116 fields.push((name, ty_str));
117 }
118 }
119
120 if !fields.is_empty() {
122 self.struct_defs.insert(qualified_name, fields);
123 }
124
125 Ok(())
126 }
127
128 fn ty_to_string(&mut self, ty: sema::Ty<'gcx>) -> Option<String> {
130 let ty = ty.peel_refs();
131 let res = match ty.kind {
132 sema::ty::TyKind::Elementary(e) => e.to_string(),
133 sema::ty::TyKind::Array(ty, size) => {
134 let inner_type = self.ty_to_string(ty)?;
135 format!("{inner_type}[{size}]")
136 }
137 sema::ty::TyKind::DynArray(ty) => {
138 let inner_type = self.ty_to_string(ty)?;
139 format!("{inner_type}[]")
140 }
141 sema::ty::TyKind::Struct(id) => {
142 self.resolve_struct_definition(id).ok()?;
144 self.get_fully_qualified_name(id)
145 }
146 sema::ty::TyKind::Udvt(ty, _) => self.ty_to_string(ty)?,
147 sema::ty::TyKind::Enum(_) => "uint8".to_string(),
149 sema::ty::TyKind::Contract(_) => "address".to_string(),
151 _ => return None,
153 };
154
155 Some(res)
156 }
157
158 fn get_fully_qualified_name(&self, id: hir::StructId) -> String {
160 let hir = self.hir();
161 let strukt = hir.strukt(id);
162 if let Some(contract_id) = strukt.contract {
163 format!("{}.{}", hir.contract(contract_id).name.as_str(), strukt.name.as_str())
164 } else {
165 strukt.name.as_str().into()
166 }
167 }
168}