rapx 0.7.40

A static analysis platform for Rust program analysis and verification
//! Place resolution: `syn::Expr` → `ContractPlace`.
//!
//! Semantic resolution of contract places (arguments, locals, field
//! projections, `iter()`/`each_element()` element projection, `unwrap_some()`
//! enum downcast) against rustc's type context.  This layer depends only on
//! `types.rs` and crate helpers, so both the property builder (`builder.rs`)
//! and the pest-based expression converter (`pest_conv.rs`) can share it
//! without a dependency cycle.

use rustc_abi::FieldIdx;
use rustc_hir::def::DefKind;
use rustc_hir::def_id::DefId;
use rustc_middle::ty::{Ty, TyCtxt, TyKind};
use syn::Expr;

use crate::helpers::fn_info::{FnKind, get_type};
use crate::helpers::name::{access_ident_recursive, get_struct_self_ty, parse_signature};

use super::types::{ContractExpr, ContractPlace, ContractProjection, PlaceBase, PropertyArg};

pub(crate) fn parse_contract_place<'tcx>(
    tcx: TyCtxt<'tcx>,
    def_id: DefId,
    expr: &Expr,
) -> Option<ContractPlace<'tcx>> {
    // Handle .iter() / .each_element() — iterate over slice elements.
    if let Expr::MethodCall(expr_method) = expr {
        if (expr_method.method == "iter" || expr_method.method == "each_element")
            && expr_method.args.is_empty()
        {
            let mut place = parse_contract_place(tcx, def_id, &expr_method.receiver)?;
            place.projections.push(ContractProjection::ForEach);
            return Some(place);
        }
    }

    // Handle .unwrap_some() method call — downcast to the Some variant.
    if let Expr::MethodCall(expr_method) = expr {
        if expr_method.method == "unwrap_some" && expr_method.args.is_empty() {
            if let Some((base, fields, recv_ty)) =
                parse_expr_into_local_and_ty(tcx, def_id, &expr_method.receiver)
            {
                let peeled_ty = recv_ty.peel_refs();
                if let TyKind::Adt(adt_def, _) = peeled_ty.kind() {
                    if adt_def.is_enum() {
                        let some_variant =
                            adt_def.variants().iter_enumerated().find_map(|(vidx, v)| {
                                if v.name.to_string() == "Some" {
                                    Some(vidx.as_usize())
                                } else {
                                    None
                                }
                            });
                        if let Some(variant_index) = some_variant {
                            let mut place = ContractPlace::local(base, fields);
                            place
                                .projections
                                .push(ContractProjection::Downcast { variant_index });
                            return Some(place);
                        }
                    }
                }
            }
        }
    }

    if let Some((base, fields, _ty)) = parse_expr_into_local_and_ty(tcx, def_id, expr) {
        return Some(ContractPlace::local(base, fields));
    }
    parse_named_place(expr)
}

fn parse_named_place<'tcx>(expr: &Expr) -> Option<ContractPlace<'tcx>> {
    // A bare `return` parses as `syn::Expr::Return { expr: None }` (a keyword,
    // not an `Expr::Path`), so `parse_named_place`'s `Expr::Path` arm below
    // never sees it.  Resolve it to the return-value place here so it works
    // inside nested numeric expressions (e.g. the slice invariant's
    // `Allocated($self, $elem, len($self))` for the return type, whose `$self`
    // is substituted with the `return` placeholder).
    if let Expr::Return(expr_return) = expr
        && expr_return.expr.is_none()
    {
        return Some(ContractPlace {
            base: PlaceBase::Return,
            projections: Vec::new(),
        });
    }
    if let Expr::Path(expr_path) = expr {
        if let Some(ident) = expr_path.path.get_ident() {
            let s = ident.to_string();
            if let Some(num_str) = s.strip_prefix("Arg_") {
                if let Ok(idx) = num_str.parse::<usize>() {
                    return Some(ContractPlace::arg(idx));
                }
            }
            if s == "return" {
                return Some(ContractPlace {
                    base: PlaceBase::Return,
                    projections: Vec::new(),
                });
            }
        }
    }
    None
}

pub(crate) fn parse_expr_into_local_and_ty<'tcx>(
    tcx: TyCtxt<'tcx>,
    def_id: DefId,
    expr: &Expr,
) -> Option<(usize, Vec<(usize, Ty<'tcx>)>, Ty<'tcx>)> {
    if let Some((base_ident, fields)) = access_ident_recursive(expr) {
        return resolve_place_from_ident(tcx, def_id, &base_ident, &fields);
    }
    None
}

/// Resolve a place given its base identifier and field-name list directly,
/// without going through a `syn` expression.  Used by the pest converter.
pub(crate) fn resolve_place_from_ident<'tcx>(
    tcx: TyCtxt<'tcx>,
    def_id: DefId,
    base_ident: &str,
    fields: &[String],
) -> Option<(usize, Vec<(usize, Ty<'tcx>)>, Ty<'tcx>)> {
    let (param_names, param_tys) = parse_signature(tcx, def_id);
    if param_names[0] != "0" {
        if let Some(param_index) = param_names.iter().position(|name| name == base_ident) {
            return resolve_projection_from_base_ident(
                tcx,
                base_ident.to_string(),
                fields.to_vec(),
                param_index + 1,
                param_tys[param_index],
            );
        }
    }

    if let Some(struct_ty) = get_struct_self_ty(tcx, def_id) {
        return resolve_projection_from_struct_ident(
            tcx,
            def_id,
            base_ident.to_string(),
            fields.to_vec(),
            struct_ty,
        );
    }
    None
}

/// Walk a list of field names from `current_ty`, appending `(index, ty)` pairs.
fn walk_fields<'tcx>(
    tcx: TyCtxt<'tcx>,
    mut current_ty: Ty<'tcx>,
    mut field_indices: Vec<(usize, Ty<'tcx>)>,
    fields: &[String],
) -> Option<(Vec<(usize, Ty<'tcx>)>, Ty<'tcx>)> {
    for field_name in fields {
        let (field_idx, field_ty) = resolve_next_field(tcx, current_ty, field_name)?;
        current_ty = field_ty;
        field_indices.push((field_idx, current_ty));
    }
    Some((field_indices, current_ty))
}

fn resolve_projection_from_base_ident<'tcx>(
    tcx: TyCtxt<'tcx>,
    _base_ident: String,
    fields: Vec<String>,
    base_local: usize,
    base_ty: Ty<'tcx>,
) -> Option<(usize, Vec<(usize, Ty<'tcx>)>, Ty<'tcx>)> {
    let (field_indices, current_ty) = walk_fields(tcx, base_ty, Vec::new(), &fields)?;
    Some((base_local, field_indices, current_ty))
}

fn resolve_projection_from_struct_ident<'tcx>(
    tcx: TyCtxt<'tcx>,
    def_id: DefId,
    base_ident: String,
    fields: Vec<String>,
    struct_ty: Ty<'tcx>,
) -> Option<(usize, Vec<(usize, Ty<'tcx>)>, Ty<'tcx>)> {
    let (field_idx, field_ty) = resolve_next_field(tcx, struct_ty, &base_ident)?;
    let (mut field_indices, current_ty) =
        walk_fields(tcx, field_ty, vec![(field_idx, field_ty)], &fields)?;

    let base_local = if get_type(tcx, def_id) == FnKind::Constructor {
        0
    } else {
        1
    };

    // For a "wrapped" constructor (`Result<Self>` / `Option<Self>`), the struct
    // lives inside the `Ok`/`Some` variant (field 0 of the enum). Prepend that
    // field access so the invariant's place resolves through the variant's data
    // (e.g. `ptr` -> `Return.Field(0).Field(0)`).
    if base_local == 0 && crate::helpers::fn_info::returns_wrapped_self(tcx, def_id) {
        field_indices.insert(0, (0, struct_ty));
    }

    Some((base_local, field_indices, current_ty))
}

fn resolve_next_field<'tcx>(
    tcx: TyCtxt<'tcx>,
    base_ty: Ty<'tcx>,
    field_name: &str,
) -> Option<(usize, Ty<'tcx>)> {
    let peeled_ty = base_ty.peel_refs();
    if let TyKind::Adt(adt_def, arg_list) = *peeled_ty.kind() {
        if !adt_def.is_struct() && !adt_def.is_union() {
            return None;
        }
        let variant = adt_def.non_enum_variant();
        if let Ok(field_idx) = field_name.parse::<usize>() {
            if field_idx < variant.fields.len() {
                let field_ty = crate::helpers::mir_utils::field_ty(
                    tcx,
                    &variant.fields[FieldIdx::from_usize(field_idx)],
                    arg_list,
                );
                return Some((field_idx, field_ty));
            }
        }
        if let Some((idx, _)) = variant
            .fields
            .iter()
            .enumerate()
            .find(|(_, f)| f.ident(tcx).name.to_string() == field_name)
        {
            let field_ty = crate::helpers::mir_utils::field_ty(
                tcx,
                &variant.fields[FieldIdx::from_usize(idx)],
                arg_list,
            );
            return Some((idx, field_ty));
        }
    }
    None
}

/// Strip `ForEach` from a property arg and return the container place
/// (without the projection) if `ForEach` was present.
pub(crate) fn strip_for_each<'tcx>(arg: &mut PropertyArg<'tcx>) -> Option<ContractPlace<'tcx>> {
    if let PropertyArg::Expr(ContractExpr::Place(place)) = arg {
        if place
            .projections
            .iter()
            .any(|p| matches!(p, ContractProjection::ForEach))
        {
            let mut container = place.clone();
            container
                .projections
                .retain(|p| !matches!(p, ContractProjection::ForEach));
            place
                .projections
                .retain(|p| !matches!(p, ContractProjection::ForEach));
            return Some(container);
        }
    }
    None
}

/// Check if the given expression refers to a function parameter whose type is
/// an array.  If so, return a `ContractPlace` for that parameter to be used as
/// the `for_each` container.
pub(crate) fn detect_array_for_each<'tcx>(
    tcx: TyCtxt<'tcx>,
    def_id: DefId,
    expr: &Expr,
) -> Option<ContractPlace<'tcx>> {
    let place = parse_contract_place(tcx, def_id, expr)?;
    let param_idx = match place.base {
        PlaceBase::Arg(n) => n,
        PlaceBase::Local(n) => {
            // Local 0 = return, locals 1.. = parameters
            n.checked_sub(1)?
        }
        _ => return None,
    };
    let fn_sig = tcx.fn_sig(def_id).instantiate_identity().skip_binder();
    if let Some(arg_ty) = fn_sig.inputs().get(param_idx) {
        if matches!(arg_ty.kind(), TyKind::Array(..)) {
            return Some(ContractPlace {
                base: PlaceBase::Arg(param_idx),
                projections: vec![],
            });
        }
    }
    None
}

/// The field name selected by the first `Field` projection of `place` on
/// `adt_def_id` (e.g. `ptr` in `self.ptr`), if any.
pub(crate) fn field_name_from_place<'tcx>(
    tcx: TyCtxt<'tcx>,
    adt_def_id: DefId,
    place: &ContractPlace<'tcx>,
) -> Option<String> {
    // `adt_def_id` may be a function/impl def-id (e.g. from a `requires`
    // annotation on a method); only resolve fields on real ADTs.
    if !matches!(
        tcx.def_kind(adt_def_id),
        DefKind::Struct | DefKind::Enum | DefKind::Union
    ) {
        return None;
    }
    let idx = place.projections.iter().find_map(|p| match p {
        ContractProjection::Field { index, .. } => Some(*index),
        _ => None,
    })?;
    let adt = tcx.adt_def(adt_def_id);
    let field = adt.non_enum_variant().fields.get(FieldIdx::from_usize(idx))?;
    Some(field.name.to_string())
}

/// The field name targeted by an `Allocated`/`Owning`-style property argument,
/// whether it was parsed as a bare identifier or as a resolved place.
pub(crate) fn field_name_from_arg<'tcx>(
    tcx: TyCtxt<'tcx>,
    adt_def_id: DefId,
    arg: &PropertyArg<'tcx>,
) -> Option<String> {
    match arg {
        PropertyArg::Ident(s) => Some(s.clone()),
        PropertyArg::Expr(ContractExpr::Place(place)) => {
            field_name_from_place(tcx, adt_def_id, place)
        }
        _ => None,
    }
}