1use rustc_hir::def_id::DefId;
9use rustc_middle::ty::TyCtxt;
10
11use super::types::{
12 ContractExpr, ContractPlace, ContractProjection, NumericBinOp, NumericPredicate,
13 NumericUnaryOp, PlaceBase, Property, PropertyArg, PropertyKind, RelOp,
14};
15
16fn local_snippet<'tcx>(
19 tcx: TyCtxt<'tcx>,
20 fn_def_id: Option<DefId>,
21 mir_local: usize,
22) -> Option<String> {
23 let fn_def_id = fn_def_id?;
24 if !tcx.is_mir_available(fn_def_id) {
25 return None;
26 }
27 let body = tcx.optimized_mir(fn_def_id);
28 if mir_local >= body.local_decls.len() {
29 return None;
30 }
31 let local = rustc_middle::mir::Local::from_usize(mir_local);
32 let span = body.local_decls[local].source_info.span;
33 tcx.sess.source_map().span_to_snippet(span).ok()
34}
35
36impl<'tcx> ContractPlace<'tcx> {
37 pub(crate) fn display_user_friendly(
38 &self,
39 tcx: TyCtxt<'tcx>,
40 struct_def_id: Option<DefId>,
41 fn_def_id: Option<DefId>,
42 ) -> String {
43 let has_projections = !self.projections.is_empty();
44
45 let base_str = match self.base {
46 PlaceBase::Return => {
47 if has_projections {
48 String::new()
49 } else {
50 "return".to_string()
51 }
52 }
53 PlaceBase::Arg(idx) => local_snippet(tcx, fn_def_id, idx + 1)
54 .filter(|s| !s.is_empty())
55 .unwrap_or_else(|| format!("arg{}", idx)),
56 PlaceBase::Local(n) => {
57 if n == 0 {
58 "return".to_string()
59 } else {
60 local_snippet(tcx, fn_def_id, n).unwrap_or_else(|| format!("arg{}", n))
61 }
62 }
63 };
64
65 let base_str = base_str
66 .strip_prefix("&mut ")
67 .unwrap_or(&base_str)
68 .to_string();
69 let base_str = base_str.strip_prefix("&").unwrap_or(&base_str).to_string();
70
71 if self.projections.is_empty() {
72 return base_str;
73 }
74
75 let mut result = base_str;
76 for projection in &self.projections {
77 match projection {
78 ContractProjection::Field { index, ty: _ } => {
79 let field_name =
80 crate::helpers::name::resolve_field_name(tcx, index, struct_def_id);
81 if result.is_empty() {
82 result = field_name;
83 } else {
84 result.push_str(&format!(".{}", field_name));
85 }
86 }
87 ContractProjection::Downcast { .. } => {
88 result.push_str(".unwrap_some()");
89 }
90 ContractProjection::ForEach => {
91 result.push_str(".iter()");
92 }
93 }
94 }
95 result
96 }
97}
98
99impl<'tcx> NumericPredicate<'tcx> {
100 pub(crate) fn display_user_friendly(
101 &self,
102 tcx: TyCtxt<'tcx>,
103 struct_def_id: Option<DefId>,
104 fn_def_id: Option<DefId>,
105 ) -> String {
106 let op_str = match self.op {
107 RelOp::Eq => "==",
108 RelOp::Ne => "!=",
109 RelOp::Lt => "<",
110 RelOp::Le => "<=",
111 RelOp::Gt => ">",
112 RelOp::Ge => ">=",
113 };
114 format!(
115 "{} {} {}",
116 display_expr_user_friendly(&self.lhs, tcx, struct_def_id, fn_def_id),
117 op_str,
118 display_expr_user_friendly(&self.rhs, tcx, struct_def_id, fn_def_id),
119 )
120 }
121}
122
123pub(crate) fn display_expr_user_friendly<'tcx>(
124 expr: &ContractExpr<'tcx>,
125 tcx: TyCtxt<'tcx>,
126 struct_def_id: Option<DefId>,
127 fn_def_id: Option<DefId>,
128) -> String {
129 match expr {
130 ContractExpr::Const(n) => format!("{n}"),
131 ContractExpr::ConstParam { name, .. } => name.clone(),
132 ContractExpr::Place(p) => p.display_user_friendly(tcx, struct_def_id, fn_def_id),
133 ContractExpr::SizeOf(ty) => format!("size_of({ty})"),
134 ContractExpr::AlignOf(ty) => format!("align_of({ty})"),
135 ContractExpr::Len(e) => {
136 format!(
137 "len({})",
138 display_expr_user_friendly(e, tcx, struct_def_id, fn_def_id)
139 )
140 }
141 ContractExpr::IndexAccess { slice, index } => {
142 format!(
143 "{}[{}]",
144 display_expr_user_friendly(slice, tcx, struct_def_id, fn_def_id),
145 display_expr_user_friendly(index, tcx, struct_def_id, fn_def_id),
146 )
147 }
148 ContractExpr::Binary { op, lhs, rhs } => {
149 let lhs_str = display_expr_user_friendly(lhs, tcx, struct_def_id, fn_def_id);
150 let rhs_str = display_expr_user_friendly(rhs, tcx, struct_def_id, fn_def_id);
151 let op_str = match op {
152 NumericBinOp::Min => return format!("min({lhs_str}, {rhs_str})"),
153 NumericBinOp::Max => return format!("max({lhs_str}, {rhs_str})"),
154 NumericBinOp::Add => "+",
155 NumericBinOp::Sub => "-",
156 NumericBinOp::Mul => "*",
157 NumericBinOp::Div => "/",
158 NumericBinOp::Rem => "%",
159 NumericBinOp::BitAnd => "&",
160 NumericBinOp::BitOr => "|",
161 NumericBinOp::BitXor => "^",
162 };
163 format!("{lhs_str} {op_str} {rhs_str}")
164 }
165 ContractExpr::Unary { op, expr } => {
166 let op_str = match op {
167 NumericUnaryOp::Not => "!",
168 NumericUnaryOp::Neg => "-",
169 };
170 format!(
171 "{}{}",
172 op_str,
173 display_expr_user_friendly(expr, tcx, struct_def_id, fn_def_id),
174 )
175 }
176 ContractExpr::If {
177 cond,
178 then_expr,
179 else_expr,
180 } => {
181 format!(
182 "if {} {{ {} }} else {{ {} }}",
183 cond.display_user_friendly(tcx, struct_def_id, fn_def_id),
184 display_expr_user_friendly(then_expr, tcx, struct_def_id, fn_def_id),
185 display_expr_user_friendly(else_expr, tcx, struct_def_id, fn_def_id),
186 )
187 }
188 _ => format!("{:?}", expr),
189 }
190}
191
192impl<'tcx> PropertyArg<'tcx> {
193 pub(crate) fn display_for_report(
194 &self,
195 tcx: TyCtxt<'tcx>,
196 struct_def_id: Option<DefId>,
197 fn_def_id: Option<DefId>,
198 ) -> String {
199 match self {
200 PropertyArg::Ty(ty) => format!("{}", ty),
201 PropertyArg::Expr(expr) => {
202 display_expr_user_friendly(expr, tcx, struct_def_id, fn_def_id)
203 }
204 PropertyArg::Predicates(preds) => {
205 let p: Vec<_> = preds
206 .iter()
207 .map(|pred| pred.display_user_friendly(tcx, struct_def_id, fn_def_id))
208 .collect();
209 p.join(" && ")
210 }
211 PropertyArg::Ident(s) => s.clone(),
212 PropertyArg::Region(r) => format!("'{r}"),
213 }
214 }
215}
216
217impl<'tcx> Property<'tcx> {
218 pub(crate) fn display_for_report(
219 &self,
220 tcx: TyCtxt<'tcx>,
221 struct_def_id: Option<DefId>,
222 fn_def_id: Option<DefId>,
223 ) -> String {
224 if let Some(origin) = self.origin() {
227 return format!("{}({})", origin.name, origin.args.join(", "));
228 }
229
230 let kind_str = match self.kind() {
231 Some(k) => format!("{k:?}"),
232 None => {
233 if self.is_and() {
234 "And".to_string()
235 } else {
236 "Or".to_string()
237 }
238 }
239 };
240
241 if matches!(self.kind(), Some(PropertyKind::InBound))
242 && matches!(
243 self.args().first(),
244 Some(PropertyArg::Expr(ContractExpr::IndexAccess { .. }))
245 )
246 {
247 if let Some(PropertyArg::Expr(ContractExpr::IndexAccess { slice, index })) =
248 self.args().first()
249 {
250 let slice_str = display_expr_user_friendly(slice, tcx, struct_def_id, fn_def_id);
251 let index_str = display_expr_user_friendly(index, tcx, struct_def_id, fn_def_id);
252 return format!("{}({}, {})", kind_str, slice_str, index_str);
253 }
254 }
255
256 if matches!(self.kind(), Some(PropertyKind::ValidNum))
257 && let Some(PropertyArg::Predicates(preds)) = self.args().first()
258 {
259 let inner: Vec<String> = preds
260 .iter()
261 .map(|pred| pred.display_user_friendly(tcx, struct_def_id, fn_def_id))
262 .collect();
263 if inner.is_empty() {
264 return format!("{}", kind_str);
265 }
266 return format!("{}({})", kind_str, inner.join(", "));
267 }
268
269 let args: Vec<String> = self
270 .args()
271 .iter()
272 .map(|arg| arg.display_for_report(tcx, struct_def_id, fn_def_id))
273 .collect();
274 if args.is_empty() {
275 kind_str
276 } else {
277 format!("{}({})", kind_str, args.join(", "))
278 }
279 }
280}