use quote::ToTokens;
use rustc_hir::def::DefKind;
use rustc_hir::def_id::DefId;
use rustc_middle::ty::{GenericParamDefKind, Ty, TyCtxt};
use syn::{Expr, Lit};
use crate::helpers::fn_info::parse_expr_into_number;
use crate::helpers::name::{access_ident_recursive, match_ty_with_ident};
use super::place;
use super::types::{ContractExpr, ContractPlace, NumericPredicate, PlaceBase, PropertyArg, RelOp};
pub(crate) fn parse_contract_expr<'tcx>(
tcx: TyCtxt<'tcx>,
def_id: DefId,
expr: &Expr,
sp: &str,
) -> ContractExpr<'tcx> {
if let Expr::Field(expr_field) = expr
&& matches!(&expr_field.member, syn::Member::Named(ident) if ident == "len")
{
return ContractExpr::Len(Box::new(parse_contract_expr(
tcx,
def_id,
&expr_field.base,
sp,
)));
}
if let Expr::MethodCall(expr_method) = expr
&& expr_method.method == "len"
&& expr_method.args.is_empty()
{
return ContractExpr::Len(Box::new(parse_contract_expr(
tcx,
def_id,
&expr_method.receiver,
sp,
)));
}
if let Some(place) = place::parse_contract_place(tcx, def_id, expr) {
return ContractExpr::Place(place);
}
if let Some(e) = parse_const_param(tcx, def_id, expr) {
return e;
}
if let Some(value) = parse_builtin_const(tcx, expr) {
return ContractExpr::Const(value);
}
if let Some(value) = parse_expr_into_number(expr) {
return ContractExpr::new_value(value);
}
if let Expr::Path(expr_path) = expr
&& let Some(ident) = expr_path.path.get_ident()
&& let Some(value) = crate::helpers::mir_utils::resolve_const_item_value(
tcx,
&ident.to_string(),
)
{
return ContractExpr::Const(value);
}
rap_debug!(
"Numeric expression in {:?} could not be resolved: {:?}",
sp,
expr
);
ContractExpr::Unknown
}
pub(crate) fn resolve_type_name<'tcx>(
tcx: TyCtxt<'tcx>,
def_id: DefId,
name: &str,
) -> Option<Ty<'tcx>> {
if name == "Self" {
return match tcx.def_kind(def_id) {
DefKind::Struct | DefKind::Enum | DefKind::Union => {
Some(tcx.type_of(def_id).skip_binder())
}
_ => {
let sig = tcx.fn_sig(def_id).skip_binder();
sig.inputs().skip_binder().first().copied()
}
};
}
match_ty_with_ident(tcx, def_id, name.to_string())
}
pub(crate) fn int_type_min_max<'tcx>(tcx: TyCtxt<'tcx>, ty: Ty<'tcx>) -> Option<(u128, u128)> {
use rustc_middle::ty::IntTy;
use rustc_middle::ty::UintTy;
let bits: u32 = match ty.kind() {
rustc_middle::ty::TyKind::Uint(ut) => match ut {
UintTy::U8 => 8,
UintTy::U16 => 16,
UintTy::U32 => 32,
UintTy::U64 => 64,
UintTy::U128 => 128,
UintTy::Usize => tcx.data_layout.pointer_size().bits() as u32,
},
rustc_middle::ty::TyKind::Int(it) => match it {
IntTy::I8 => 8,
IntTy::I16 => 16,
IntTy::I32 => 32,
IntTy::I64 => 64,
IntTy::I128 => 128,
IntTy::Isize => tcx.data_layout.pointer_size().bits() as u32,
},
_ => return None,
};
if bits == 0 {
return None;
}
match ty.kind() {
rustc_middle::ty::TyKind::Uint(_) => {
let max = if bits == 128 {
u128::MAX
} else {
(1u128 << bits) - 1
};
Some((0, max))
}
rustc_middle::ty::TyKind::Int(_) => {
let max = (1u128 << (bits - 1)) - 1;
let min = max + 1;
Some((min, max))
}
_ => None,
}
}
fn parse_builtin_const<'tcx>(tcx: TyCtxt<'tcx>, expr: &Expr) -> Option<u128> {
let Expr::Path(expr_path) = expr else {
return None;
};
let mut segments = expr_path.path.segments.iter();
let first = segments.next()?.ident.to_string();
let second = segments.next()?.ident.to_string();
if segments.next().is_some() || second != "MAX" {
return None;
}
let pointer_bits = tcx.data_layout.pointer_size().bits();
match first.as_str() {
"isize" => Some((1_u128 << (pointer_bits - 1)) - 1),
"usize" => Some((1_u128 << pointer_bits) - 1),
_ => None,
}
}
fn parse_const_param<'tcx>(
tcx: TyCtxt<'tcx>,
def_id: DefId,
expr: &Expr,
) -> Option<ContractExpr<'tcx>> {
let Expr::Path(expr_path) = expr else {
return None;
};
let ident = expr_path.path.get_ident()?.to_string();
let mut generics = Some(tcx.generics_of(def_id));
while let Some(current) = generics {
if let Some(param) = current.own_params.iter().find(|param| {
matches!(param.kind, GenericParamDefKind::Const { .. }) && param.name.as_str() == ident
}) {
return Some(ContractExpr::ConstParam {
index: param.index,
name: ident,
});
}
generics = current.parent.map(|parent| tcx.generics_of(parent));
}
None
}
pub(crate) fn parse_type<'tcx>(
tcx: TyCtxt<'tcx>,
def_id: DefId,
expr: &Expr,
sp: &str,
) -> Option<Ty<'tcx>> {
if let Expr::Verbatim(ts) = expr {
let name = syn::parse2::<syn::Type>(ts.clone())
.ok()
.and_then(|ty| outermost_type_ident(&ty));
let Some(name) = name else {
rap_debug!("Incorrect expression for the type of {:?} Tag!", sp);
return None;
};
let ty = resolve_ty_ident(tcx, def_id, &name);
if ty.is_none() {
rap_debug!("Cannot get type in {:?} Tag!", sp);
}
return ty;
}
let ty_ident_full = access_ident_recursive(expr);
if ty_ident_full.is_none() {
rap_debug!("Incorrect expression for the type of {:?} Tag!", sp);
return None;
}
let ty_ident = ty_ident_full.unwrap().0;
let ty = resolve_ty_ident(tcx, def_id, &ty_ident);
if ty.is_none() {
rap_debug!("Cannot get type in {:?} Tag!", sp);
}
ty
}
fn resolve_ty_ident<'tcx>(tcx: TyCtxt<'tcx>, def_id: DefId, name: &str) -> Option<Ty<'tcx>> {
if name == "Self" {
resolve_type_name(tcx, def_id, name)
} else {
match_ty_with_ident(tcx, def_id, name.to_string())
}
}
fn outermost_type_ident(ty: &syn::Type) -> Option<String> {
match ty {
syn::Type::Path(tp) if tp.qself.is_none() => {
tp.path.segments.last().map(|s| s.ident.to_string())
}
_ => None,
}
}
pub(crate) fn parse_target_arg<'tcx>(
tcx: TyCtxt<'tcx>,
def_id: DefId,
expr: &Expr,
) -> PropertyArg<'tcx> {
if matches!(expr, Expr::Return(_)) {
return PropertyArg::Expr(ContractExpr::Place(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 s != "return"
&& !s.starts_with("Arg_")
&& place::parse_expr_into_local_and_ty(tcx, def_id, expr).is_none()
{
return PropertyArg::Ident(s);
}
}
}
place::parse_contract_place(tcx, def_id, expr)
.map(|p| PropertyArg::Expr(ContractExpr::Place(p)))
.unwrap_or_else(|| PropertyArg::Expr(parse_contract_expr(tcx, def_id, expr, "target")))
}
pub(crate) fn parse_valid_num<'tcx>(
tcx: TyCtxt<'tcx>,
def_id: DefId,
exprs: &[Expr],
) -> Vec<NumericPredicate<'tcx>> {
match exprs {
[] => Vec::new(),
[expr] => parse_numeric_predicate(tcx, def_id, expr)
.into_iter()
.collect(),
[value, range, ..] => {
if let Some(predicates) = parse_interval_predicates(tcx, def_id, value, range) {
predicates
} else {
parse_numeric_predicate(tcx, def_id, value)
.into_iter()
.collect()
}
}
}
}
fn parse_numeric_predicate<'tcx>(
tcx: TyCtxt<'tcx>,
def_id: DefId,
expr: &Expr,
) -> Option<NumericPredicate<'tcx>> {
let text = expr.to_token_stream().to_string();
super::pest_conv::parse_predicate_pest(tcx, def_id, &text)
}
pub(crate) fn expr_to_pest<'tcx>(
tcx: TyCtxt<'tcx>,
def_id: DefId,
expr: &Expr,
) -> ContractExpr<'tcx> {
let text = expr.to_token_stream().to_string();
super::pest_conv::parse_expr_pest(tcx, def_id, &text)
}
fn parse_interval_predicates<'tcx>(
tcx: TyCtxt<'tcx>,
def_id: DefId,
value: &Expr,
range: &Expr,
) -> Option<Vec<NumericPredicate<'tcx>>> {
match range {
Expr::Array(array) if array.elems.len() == 2 => {
let mut elems = array.elems.iter();
let lower = elems.next().unwrap();
let upper = elems.next().unwrap();
Some(build_interval_predicates(
tcx, def_id, value, lower, true, upper, true,
))
}
Expr::Lit(expr_lit) => match &expr_lit.lit {
Lit::Str(range_lit) => {
parse_string_interval(tcx, def_id, value, &range_lit.value())
}
Lit::Int(int_lit) => {
let n = int_lit.base10_parse::<u64>().ok()?;
let n_expr = syn::parse_str::<Expr>(&n.to_string()).ok()?;
Some(build_interval_predicates(
tcx, def_id, value, &n_expr, true, &n_expr, true,
))
}
_ => None,
},
_ => None,
}
}
fn parse_string_interval<'tcx>(
tcx: TyCtxt<'tcx>,
def_id: DefId,
value: &Expr,
raw_range: &str,
) -> Option<Vec<NumericPredicate<'tcx>>> {
let trimmed = raw_range.trim();
if trimmed.len() < 3 {
return None;
}
let lower_inclusive = trimmed.starts_with('[');
let upper_inclusive = trimmed.ends_with(']');
if !(lower_inclusive || trimmed.starts_with('('))
|| !(upper_inclusive || trimmed.ends_with(')'))
{
return None;
}
let body = &trimmed[1..trimmed.len() - 1];
let (lower_raw, upper_raw) = body.split_once(',')?;
let lower_raw = lower_raw.trim();
let upper_raw = upper_raw.trim();
if lower_raw.is_empty() && upper_raw.is_empty() {
return None;
}
let value_expr = expr_to_pest(tcx, def_id, value);
let mut predicates = Vec::with_capacity(2);
if !lower_raw.is_empty() {
let lower = syn::parse_str::<Expr>(lower_raw).ok()?;
predicates.push(NumericPredicate::new(
expr_to_pest(tcx, def_id, &lower),
if lower_inclusive { RelOp::Le } else { RelOp::Lt },
value_expr.clone(),
));
}
if !upper_raw.is_empty() {
let upper = syn::parse_str::<Expr>(upper_raw).ok()?;
predicates.push(NumericPredicate::new(
value_expr,
if upper_inclusive { RelOp::Le } else { RelOp::Lt },
expr_to_pest(tcx, def_id, &upper),
));
}
Some(predicates)
}
fn build_interval_predicates<'tcx>(
tcx: TyCtxt<'tcx>,
def_id: DefId,
value: &Expr,
lower: &Expr,
lower_inclusive: bool,
upper: &Expr,
upper_inclusive: bool,
) -> Vec<NumericPredicate<'tcx>> {
let value_expr = expr_to_pest(tcx, def_id, value);
let lower_expr = expr_to_pest(tcx, def_id, lower);
let upper_expr = expr_to_pest(tcx, def_id, upper);
vec![
NumericPredicate::new(
lower_expr,
if lower_inclusive {
RelOp::Le
} else {
RelOp::Lt
},
value_expr.clone(),
),
NumericPredicate::new(
value_expr,
if upper_inclusive {
RelOp::Le
} else {
RelOp::Lt
},
upper_expr,
),
]
}
pub(crate) fn unwrap_array_expr<'tcx>(
tcx: TyCtxt<'tcx>,
def_id: DefId,
expr: &Expr,
) -> Option<Ty<'tcx>> {
if let Expr::Array(arr) = expr
&& arr.elems.len() == 1
{
return parse_type(tcx, def_id, &arr.elems[0], "SplitTransmute");
}
parse_type(tcx, def_id, expr, "SplitTransmute")
}