Skip to main content

rapx/verify/contract/
place.rs

1//! Place resolution: `syn::Expr` → `ContractPlace`.
2//!
3//! Semantic resolution of contract places (arguments, locals, field
4//! projections, `iter()`/`each_element()` element projection, `unwrap_some()`
5//! enum downcast) against rustc's type context.  This layer depends only on
6//! `types.rs` and crate helpers, so both the property builder (`builder.rs`)
7//! and the pest-based expression converter (`pest_conv.rs`) can share it
8//! without a dependency cycle.
9
10use 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    // Handle .iter() / .each_element() — iterate over slice elements.
26    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    // Handle .unwrap_some() method call — downcast to the Some variant.
37    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
115/// Resolve a place given its base identifier and field-name list directly,
116/// without going through a `syn` expression.  Used by the pest converter.
117pub(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    // For a "wrapped" constructor (`Result<Self>` / `Option<Self>`), the struct
197    // lives inside the `Ok`/`Some` variant (field 0 of the enum). Prepend that
198    // field access so the invariant's place resolves through the variant's data
199    // (e.g. `ptr` -> `Return.Field(0).Field(0)`).
200    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
249/// Strip `IterElements` from a property arg and return the container place
250/// (without the projection) if `IterElements` was present.
251pub(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
265/// Check if the given expression refers to a function parameter whose type is
266/// an array.  If so, return a `ContractPlace` for that parameter to be used as
267/// the `for_each` container.
268pub(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            // Local 0 = return, locals 1.. = parameters
278            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}