1use rustc_abi::FieldIdx;
11use rustc_hir::def_id::DefId;
12use rustc_middle::ty::{Ty, TyCtxt, TyKind};
13use safety_parser::syn::Expr;
14
15use crate::helpers::fn_info::{FnKind, get_type};
16use crate::helpers::name::{access_ident_recursive, get_struct_self_ty, parse_signature};
17
18use super::types::{ContractExpr, ContractPlace, ContractProjection, PlaceBase, PropertyArg};
19
20pub(crate) fn parse_contract_place<'tcx>(
21 tcx: TyCtxt<'tcx>,
22 def_id: DefId,
23 expr: &Expr,
24) -> Option<ContractPlace<'tcx>> {
25 if let Expr::MethodCall(expr_method) = expr {
27 if (expr_method.method == "iter" || expr_method.method == "each_element")
28 && expr_method.args.is_empty()
29 {
30 let mut place = parse_contract_place(tcx, def_id, &expr_method.receiver)?;
31 place.projections.push(ContractProjection::IterElements);
32 return Some(place);
33 }
34 }
35
36 if let Expr::MethodCall(expr_method) = expr {
38 if expr_method.method == "unwrap_some" && expr_method.args.is_empty() {
39 if let Some((base, fields, recv_ty)) =
40 parse_expr_into_local_and_ty(tcx, def_id, &expr_method.receiver)
41 {
42 let peeled_ty = recv_ty.peel_refs();
43 if let TyKind::Adt(adt_def, _) = peeled_ty.kind() {
44 if adt_def.is_enum() {
45 let some_variant =
46 adt_def.variants().iter_enumerated().find_map(|(vidx, v)| {
47 if v.name.to_string() == "Some" {
48 Some(vidx.as_usize())
49 } else {
50 None
51 }
52 });
53 if let Some(variant_index) = some_variant {
54 let mut projections: Vec<ContractProjection> = fields
55 .into_iter()
56 .map(|(index, ty)| ContractProjection::Field {
57 index,
58 ty: Some(ty),
59 })
60 .collect();
61 projections.push(ContractProjection::Downcast { variant_index });
62 let base_enum = if base == 0 {
63 PlaceBase::Return
64 } else {
65 PlaceBase::Local(base)
66 };
67 return Some(ContractPlace {
68 base: base_enum,
69 projections,
70 });
71 }
72 }
73 }
74 }
75 }
76 }
77
78 if let Some((base, fields, _ty)) = parse_expr_into_local_and_ty(tcx, def_id, expr) {
79 return Some(ContractPlace::local(base, fields));
80 }
81 parse_named_place(expr)
82}
83
84fn parse_named_place<'tcx>(expr: &Expr) -> Option<ContractPlace<'tcx>> {
85 if let Expr::Path(expr_path) = expr {
86 if let Some(ident) = expr_path.path.get_ident() {
87 let s = ident.to_string();
88 if let Some(num_str) = s.strip_prefix("Arg_") {
89 if let Ok(idx) = num_str.parse::<usize>() {
90 return Some(ContractPlace::arg(idx));
91 }
92 }
93 if s == "return" {
94 return Some(ContractPlace {
95 base: PlaceBase::Return,
96 projections: Vec::new(),
97 });
98 }
99 }
100 }
101 None
102}
103
104pub(crate) fn parse_expr_into_local_and_ty<'tcx>(
105 tcx: TyCtxt<'tcx>,
106 def_id: DefId,
107 expr: &Expr,
108) -> Option<(usize, Vec<(usize, Ty<'tcx>)>, Ty<'tcx>)> {
109 if let Some((base_ident, fields)) = access_ident_recursive(expr) {
110 return resolve_place_from_ident(tcx, def_id, &base_ident, &fields);
111 }
112 None
113}
114
115pub(crate) fn resolve_place_from_ident<'tcx>(
118 tcx: TyCtxt<'tcx>,
119 def_id: DefId,
120 base_ident: &str,
121 fields: &[String],
122) -> Option<(usize, Vec<(usize, Ty<'tcx>)>, Ty<'tcx>)> {
123 let (param_names, param_tys) = parse_signature(tcx, def_id);
124 if param_names[0] != "0" {
125 if let Some(param_index) = param_names.iter().position(|name| name == base_ident) {
126 return resolve_projection_from_base_ident(
127 tcx,
128 base_ident.to_string(),
129 fields.to_vec(),
130 param_index + 1,
131 param_tys[param_index],
132 );
133 }
134 }
135
136 if let Some(struct_ty) = get_struct_self_ty(tcx, def_id) {
137 return resolve_projection_from_struct_ident(
138 tcx,
139 def_id,
140 base_ident.to_string(),
141 fields.to_vec(),
142 struct_ty,
143 );
144 }
145 None
146}
147
148fn resolve_projection_from_base_ident<'tcx>(
149 tcx: TyCtxt<'tcx>,
150 _base_ident: String,
151 fields: Vec<String>,
152 base_local: usize,
153 base_ty: Ty<'tcx>,
154) -> Option<(usize, Vec<(usize, Ty<'tcx>)>, Ty<'tcx>)> {
155 let mut current_ty = base_ty;
156 let mut field_indices = Vec::new();
157 for field_name in fields {
158 let Some((field_idx, field_ty)) = resolve_next_field(tcx, current_ty, &field_name) else {
159 return None;
160 };
161 current_ty = field_ty;
162 field_indices.push((field_idx, current_ty));
163 }
164 Some((base_local, field_indices, current_ty))
165}
166
167fn resolve_projection_from_struct_ident<'tcx>(
168 tcx: TyCtxt<'tcx>,
169 def_id: DefId,
170 base_ident: String,
171 fields: Vec<String>,
172 struct_ty: Ty<'tcx>,
173) -> Option<(usize, Vec<(usize, Ty<'tcx>)>, Ty<'tcx>)> {
174 let Some((field_idx, field_ty)) = resolve_next_field(tcx, struct_ty, &base_ident) else {
175 return None;
176 };
177
178 let mut current_ty = field_ty;
179 let mut field_indices = vec![(field_idx, current_ty)];
180 for field_name in fields {
181 let Some((next_field_idx, next_field_ty)) =
182 resolve_next_field(tcx, current_ty, &field_name)
183 else {
184 return None;
185 };
186 current_ty = next_field_ty;
187 field_indices.push((next_field_idx, current_ty));
188 }
189
190 let base_local = if get_type(tcx, def_id) == FnKind::Constructor {
191 0
192 } else {
193 1
194 };
195
196 if base_local == 0
201 && crate::helpers::fn_info::returns_wrapped_self(tcx, def_id)
202 {
203 field_indices.insert(0, (0, struct_ty));
204 }
205
206 Some((base_local, field_indices, current_ty))
207}
208
209fn resolve_next_field<'tcx>(
210 tcx: TyCtxt<'tcx>,
211 base_ty: Ty<'tcx>,
212 field_name: &str,
213) -> Option<(usize, Ty<'tcx>)> {
214 let peeled_ty = base_ty.peel_refs();
215 if let TyKind::Adt(adt_def, arg_list) = *peeled_ty.kind() {
216 if !adt_def.is_struct() && !adt_def.is_union() {
217 return None;
218 }
219 let variant = adt_def.non_enum_variant();
220 if let Ok(field_idx) = field_name.parse::<usize>() {
221 if field_idx < variant.fields.len() {
222 #[cfg(not(rapx_ge_99))]
223 let field_ty = variant.fields[FieldIdx::from_usize(field_idx)].ty(tcx, arg_list);
224 #[cfg(rapx_ge_99)]
225 let field_ty = variant.fields[FieldIdx::from_usize(field_idx)]
226 .ty(tcx, arg_list)
227 .skip_norm_wip();
228 return Some((field_idx, field_ty));
229 }
230 }
231 if let Some((idx, _)) = variant
232 .fields
233 .iter()
234 .enumerate()
235 .find(|(_, f)| f.ident(tcx).name.to_string() == field_name)
236 {
237 #[cfg(not(rapx_ge_99))]
238 let field_ty = variant.fields[FieldIdx::from_usize(idx)].ty(tcx, arg_list);
239 #[cfg(rapx_ge_99)]
240 let field_ty = variant.fields[FieldIdx::from_usize(idx)]
241 .ty(tcx, arg_list)
242 .skip_norm_wip();
243 return Some((idx, field_ty));
244 }
245 }
246 None
247}
248
249pub(crate) fn strip_iter_elements<'tcx>(
252 arg: &mut PropertyArg<'tcx>,
253) -> Option<ContractPlace<'tcx>> {
254 if let PropertyArg::Expr(ContractExpr::Place(place)) = arg {
255 if place.projections.iter().any(|p| matches!(p, ContractProjection::IterElements)) {
256 let mut container = place.clone();
257 container.projections.retain(|p| !matches!(p, ContractProjection::IterElements));
258 place.projections.retain(|p| !matches!(p, ContractProjection::IterElements));
259 return Some(container);
260 }
261 }
262 None
263}
264
265pub(crate) fn detect_array_for_each<'tcx>(
269 tcx: TyCtxt<'tcx>,
270 def_id: DefId,
271 expr: &Expr,
272) -> Option<ContractPlace<'tcx>> {
273 let place = parse_contract_place(tcx, def_id, expr)?;
274 let param_idx = match place.base {
275 PlaceBase::Arg(n) => n,
276 PlaceBase::Local(n) => {
277 n.checked_sub(1)?
279 }
280 _ => return None,
281 };
282 let fn_sig = tcx.fn_sig(def_id).instantiate_identity().skip_binder();
283 if let Some(arg_ty) = fn_sig.inputs().get(param_idx) {
284 if matches!(arg_ty.kind(), TyKind::Array(..)) {
285 return Some(ContractPlace {
286 base: PlaceBase::Arg(param_idx),
287 projections: vec![],
288 });
289 }
290 }
291 None
292}