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::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    // Handle .iter() / .each_element() — iterate over slice elements.
27    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    // Handle .unwrap_some() method call — downcast to the Some variant.
38    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    // A bare `return` parses as `syn::Expr::Return { expr: None }` (a keyword,
75    // not an `Expr::Path`), so `parse_named_place`'s `Expr::Path` arm below
76    // never sees it.  Resolve it to the return-value place here so it works
77    // inside nested numeric expressions (e.g. the slice invariant's
78    // `Allocated($self, $elem, len($self))` for the return type, whose `$self`
79    // is substituted with the `return` placeholder).
80    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
118/// Resolve a place given its base identifier and field-name list directly,
119/// without going through a `syn` expression.  Used by the pest converter.
120pub(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
150/// Walk a list of field names from `current_ty`, appending `(index, ty)` pairs.
151fn 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    // For a "wrapped" constructor (`Result<Self>` / `Option<Self>`), the struct
193    // lives inside the `Ok`/`Some` variant (field 0 of the enum). Prepend that
194    // field access so the invariant's place resolves through the variant's data
195    // (e.g. `ptr` -> `Return.Field(0).Field(0)`).
196    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
241/// Strip `ForEach` from a property arg and return the container place
242/// (without the projection) if `ForEach` was present.
243pub(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
263/// Check if the given expression refers to a function parameter whose type is
264/// an array.  If so, return a `ContractPlace` for that parameter to be used as
265/// the `for_each` container.
266pub(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            // Local 0 = return, locals 1.. = parameters
276            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
292/// The field name selected by the first `Field` projection of `place` on
293/// `adt_def_id` (e.g. `ptr` in `self.ptr`), if any.
294pub(crate) fn field_name_from_place<'tcx>(
295    tcx: TyCtxt<'tcx>,
296    adt_def_id: DefId,
297    place: &ContractPlace<'tcx>,
298) -> Option<String> {
299    // `adt_def_id` may be a function/impl def-id (e.g. from a `requires`
300    // annotation on a method); only resolve fields on real ADTs.
301    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
319/// The field name targeted by an `Allocated`/`Owning`-style property argument,
320/// whether it was parsed as a bare identifier or as a resolved place.
321pub(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}