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