1use rustc_hir::def_id::DefId;
14use rustc_middle::ty::{Ty, TyCtxt};
15
16use super::contract::{
17 ContractExpr, NumericPredicate, Property, PropertyArg, PropertyKind, json::AnyItem,
18};
19
20pub(crate) fn build_type_invariants_from_params<'tcx>(
23 tcx: TyCtxt<'tcx>,
24 def_id: DefId,
25) -> Vec<Property<'tcx>> {
26 let db = crate::verify::contract::json::get_std_type_invariants();
27 if db.is_empty() {
28 return Vec::new();
29 }
30
31 let fn_sig = tcx.fn_sig(def_id).skip_binder();
32 let inputs = fn_sig.inputs().skip_binder();
33 let output = fn_sig.output().skip_binder();
34
35 let mut results = Vec::new();
36
37 let (param_names, _param_tys) = crate::helpers::name::parse_signature(tcx, def_id);
39
40 for (index, ¶m_ty) in inputs.iter().enumerate() {
42 if param_ty.is_primitive() {
43 continue;
44 }
45 let param_name = param_names.get(index).cloned().unwrap_or_default();
46 let (type_path, elem_ty) = type_path_key(tcx, param_ty);
47 collect_type_invariants(
48 tcx,
49 def_id,
50 db,
51 &type_path,
52 ¶m_name,
53 elem_ty,
54 &mut results,
55 );
56 }
57
58 if !output.is_unit() && !output.is_primitive() {
60 let (type_path, elem_ty) = type_path_key(tcx, output);
61 collect_type_invariants(
62 tcx,
63 def_id,
64 db,
65 &type_path,
66 "return",
67 elem_ty,
68 &mut results,
69 );
70 }
71
72 results
73}
74
75fn collect_type_invariants<'tcx>(
77 tcx: TyCtxt<'tcx>,
78 def_id: DefId,
79 db: &std::collections::HashMap<String, crate::verify::contract::json::TypeInvariantEntry>,
80 type_path: &str,
81 param_name: &str,
82 elem_ty: Option<Ty<'tcx>>,
83 results: &mut Vec<Property<'tcx>>,
84) {
85 if let Some(entry) = db.get(type_path) {
86 for prop_entry in &entry.invariants {
87 results.extend(instantiate_type_invariant(
88 tcx, def_id, prop_entry, param_name, elem_ty,
89 ));
90 }
91 }
92 for prefix in ["alloc::", "std::"] {
94 let prefixed = format!("{prefix}{type_path}");
95 if prefixed != type_path {
96 if let Some(entry) = db.get(&prefixed) {
97 for prop_entry in &entry.invariants {
98 results.extend(instantiate_type_invariant(
99 tcx, def_id, prop_entry, param_name, elem_ty,
100 ));
101 }
102 }
103 }
104 }
105}
106
107fn instantiate_type_invariant<'tcx>(
112 tcx: TyCtxt<'tcx>,
113 def_id: DefId,
114 entry: &crate::verify::contract::json::JsonProperty,
115 param_name: &str,
116 elem_ty: Option<Ty<'tcx>>,
117) -> Vec<Property<'tcx>> {
118 if let Some(disjuncts) = &entry.any {
119 if disjuncts.len() < 2 {
120 return Vec::new();
121 }
122 let mut or_disjuncts: Vec<Property<'tcx>> = Vec::with_capacity(disjuncts.len());
123 for item in disjuncts {
124 let mut group: Vec<Property<'tcx>> = Vec::new();
125 match item {
126 AnyItem::Single(e) => {
127 group.extend(instantiate_entry(tcx, def_id, e, param_name, elem_ty))
128 }
129 AnyItem::And(es) => {
130 for e in es {
131 group.extend(instantiate_entry(tcx, def_id, e, param_name, elem_ty));
132 }
133 }
134 }
135 if !group.is_empty() {
136 or_disjuncts.push(Property::conjunction(group));
137 }
138 }
139 let mut property = Property::new_or(or_disjuncts);
140 property.apply_kind(entry.kind.as_deref());
141 return vec![property];
142 }
143 instantiate_entry(tcx, def_id, entry, param_name, elem_ty)
144}
145
146const SLICE_ELEM_PLACEHOLDER: &str = "u8";
152
153fn instantiate_entry<'tcx>(
155 tcx: TyCtxt<'tcx>,
156 def_id: DefId,
157 entry: &crate::verify::contract::json::JsonProperty,
158 param_name: &str,
159 elem_ty: Option<Ty<'tcx>>,
160) -> Vec<Property<'tcx>> {
161 let mut uses_elem = false;
162 let mut exprs: Vec<syn::Expr> = Vec::new();
163 for arg_str in &entry.args {
164 let mut substituted = arg_str.replace("$self", param_name);
168 if substituted.contains("$elem") {
169 uses_elem = true;
170 substituted = substituted.replace("$elem", SLICE_ELEM_PLACEHOLDER);
171 }
172 let resolved = if is_numeric_field_access(&substituted) {
174 format!("{}.{}", param_name, substituted)
175 } else {
176 substituted
177 };
178 match syn::parse_str::<syn::Expr>(&resolved) {
179 Ok(expr) => exprs.push(expr),
180 Err(_) => {
181 rap_debug!(
182 " [type-invariant] failed to parse arg '{}' for tag {}",
183 resolved,
184 entry.tag
185 );
186 return Vec::new();
187 }
188 }
189 }
190 if exprs.is_empty() {
191 return Vec::new();
192 }
193 let mut property = Property::new(tcx, def_id, &entry.tag, &exprs);
194 property.apply_kind(entry.kind.as_deref());
195 if matches!(property.kind(), Some(PropertyKind::Unknown)) {
196 return Vec::new();
197 }
198 if uses_elem {
199 let Some(ty) = elem_ty else {
200 return Vec::new();
202 };
203 replace_ty_args(&mut property, ty);
204 }
205 vec![property]
206}
207
208fn replace_ty_args<'tcx>(property: &mut Property<'tcx>, ty: Ty<'tcx>) {
215 match property {
216 Property::Atom(atom) => {
217 for arg in &mut atom.args {
218 match arg {
219 PropertyArg::Ty(t) => *t = ty,
220 PropertyArg::Predicates(preds) => {
221 for pred in preds {
222 replace_pred_ty(pred, ty);
223 }
224 }
225 _ => {}
226 }
227 }
228 }
229 Property::And(and) => {
230 for conjunct in &mut and.conjuncts {
231 replace_ty_args(conjunct, ty);
232 }
233 }
234 Property::Or(or) => {
235 for disjunct in &mut or.disjuncts {
236 replace_ty_args(disjunct, ty);
237 }
238 }
239 }
240}
241
242fn replace_pred_ty<'tcx>(pred: &mut NumericPredicate<'tcx>, ty: Ty<'tcx>) {
244 replace_expr_ty(&mut pred.lhs, ty);
245 replace_expr_ty(&mut pred.rhs, ty);
246}
247
248fn replace_expr_ty<'tcx>(expr: &mut ContractExpr<'tcx>, ty: Ty<'tcx>) {
250 match expr {
251 ContractExpr::SizeOf(t) | ContractExpr::AlignOf(t) => *t = ty,
252 ContractExpr::Len(inner) => replace_expr_ty(inner, ty),
253 ContractExpr::IndexAccess { slice, index } => {
254 replace_expr_ty(slice, ty);
255 replace_expr_ty(index, ty);
256 }
257 ContractExpr::Binary { lhs, rhs, .. } => {
258 replace_expr_ty(lhs, ty);
259 replace_expr_ty(rhs, ty);
260 }
261 ContractExpr::Unary { expr: inner, .. } => replace_expr_ty(inner, ty),
262 ContractExpr::If {
263 cond,
264 then_expr,
265 else_expr,
266 } => {
267 replace_pred_ty(cond, ty);
268 replace_expr_ty(then_expr, ty);
269 replace_expr_ty(else_expr, ty);
270 }
271 _ => {}
272 }
273}
274
275fn is_numeric_field_access(s: &str) -> bool {
277 let trimmed = s.trim();
278 !trimmed.is_empty()
279 && trimmed
280 .split('.')
281 .all(|part| !part.is_empty() && part.chars().all(|c| c.is_ascii_digit()))
282}
283
284fn type_path_key<'tcx>(tcx: TyCtxt<'tcx>, ty: Ty<'tcx>) -> (String, Option<Ty<'tcx>>) {
292 match ty.kind() {
293 rustc_middle::ty::TyKind::Adt(adt_def, substs) => {
294 let key = crate::helpers::name::public_def_path(tcx, adt_def.did());
295 let elem_ty = substs.first().and_then(|s| s.as_type());
299 (key, elem_ty)
300 }
301 rustc_middle::ty::TyKind::Ref(_, inner, _) => match inner.kind() {
302 rustc_middle::ty::TyKind::Slice(elem) => ("[T]".to_string(), Some(*elem)),
303 _ => (format!("{ty:?}"), None),
304 },
305 _ => (format!("{ty:?}"), None),
306 }
307}