use super::common::{SmtCheckResult, SmtChecker, SmtObligation, SmtTerm};
use crate::verify::{contract::Property, helpers::Checkpoint, verifier::ForwardVisitResult};
use rustc_middle::ty::{Ty, TyKind};
pub(crate) fn check<'tcx>(
checker: &SmtChecker<'tcx>,
checkpoint: &Checkpoint<'tcx>,
property: &Property<'tcx>,
forward: &ForwardVisitResult<'tcx>,
) -> SmtCheckResult {
if let Some(reason) =
super::field_invariant::discharge_from_contract_fact_with_checkpoint(property, forward, checkpoint)
{
return SmtCheckResult::proved(format!("Init proved: {reason}"));
}
let Some(target) = checker.property_target(Some(checkpoint), property) else {
return SmtCheckResult::unknown("Init target could not be resolved");
};
let Some(required_ty) = checker.property_required_ty(Some(checkpoint), property) else {
return SmtCheckResult::unknown("Init type could not be resolved");
};
if checker.maybeuninit_covering_init(checkpoint, &target, required_ty, forward) {
return SmtCheckResult::proved(format!(
"Init proved: MaybeUninit<[{required_ty:?}; N]> fully initialized by covering loop"
));
}
let Some(elements_expr) = checker.property_len_expr(Some(checkpoint), property) else {
return SmtCheckResult::unknown("Init element-count argument could not be resolved");
};
let Some(elements_term) =
checker.contract_expr_to_smt_term(checkpoint.caller, &elements_expr, None)
else {
return SmtCheckResult::unknown("Init element-count argument could not be lowered to SMT");
};
let elem_size = compute_elem_size(checker, checkpoint.caller, required_ty);
let result = checker.prove_obligation(
checkpoint,
forward,
SmtObligation::Initialized {
place: target,
ty_name: format!("{required_ty:?}"),
elements: elements_term,
elem_size,
array_elem_size: array_elem_size(checker, checkpoint.caller, required_ty),
array_len_term: array_len_term(checker, required_ty),
},
property.null_guard.as_ref(),
);
if result.result == crate::verify::report::CheckResult::Unknown {
if let Some(reason) =
super::provenance::pedigree_proof(checker, checkpoint, property, forward, true)
{
return SmtCheckResult::proved(format!("Init proved: {reason}"));
}
}
result
}
fn compute_elem_size<'tcx>(
checker: &SmtChecker<'tcx>,
caller: rustc_hir::def_id::DefId,
ty: Ty<'tcx>,
) -> Option<u64> {
if let Some((_, size)) = checker.type_layout(caller, ty) {
return Some(size);
}
None
}
fn array_elem_size<'tcx>(
checker: &SmtChecker<'tcx>,
caller: rustc_hir::def_id::DefId,
ty: Ty<'tcx>,
) -> Option<u64> {
if let TyKind::Array(elem, _) = ty.kind() {
checker.type_layout(caller, *elem).map(|(_, s)| s)
} else {
None
}
}
fn array_len_term<'tcx>(checker: &SmtChecker<'tcx>, ty: Ty<'tcx>) -> Option<SmtTerm> {
use rustc_middle::ty::ConstKind;
if let TyKind::Array(_, len) = ty.kind() {
if let Some(val) = len.try_to_target_usize(checker.tcx) {
return Some(SmtTerm::Const(val));
}
if let ConstKind::Param(param) = len.kind() {
return Some(SmtTerm::ConstParam(param.name.to_string()));
}
Some(SmtTerm::ConstParam(format!("Ty(usize, {len})")))
} else {
None
}
}
pub(crate) fn check_for_checkpoint<'tcx>(
checker: &SmtChecker<'tcx>,
caller: rustc_hir::def_id::DefId,
property: &Property<'tcx>,
forward: &ForwardVisitResult<'tcx>,
) -> SmtCheckResult {
let Some(target) = checker.property_target(None, property) else {
return SmtCheckResult::unknown("Init target could not be resolved");
};
let Some(required_ty) = checker.property_required_ty(None, property) else {
return SmtCheckResult::unknown("Init type could not be resolved");
};
let Some(elements_expr) = checker.property_len_expr(None, property) else {
return SmtCheckResult::unknown("Init element-count argument could not be resolved");
};
let Some(elements) = checker.contract_expr_to_smt_term(caller, &elements_expr, None) else {
return SmtCheckResult::unknown("Init element-count argument could not be lowered to SMT");
};
let result = checker.prove_obligation_for_checkpoint(
caller,
forward,
SmtObligation::Initialized {
place: target,
ty_name: format!("{required_ty:?}"),
elements,
elem_size: compute_elem_size(checker, caller, required_ty),
array_elem_size: array_elem_size(checker, caller, required_ty),
array_len_term: array_len_term(checker, required_ty),
},
);
if matches!(result.result, crate::verify::report::CheckResult::Proved) {
return result;
}
if let Some(reason) = super::field_invariant::discharge_from_contract_fact(property, forward) {
return SmtCheckResult::proved(format!("Init proved: {reason}"));
}
result
}