From 4eae8924b014f4f5fa518b8915ea122e79e4ff9e Mon Sep 17 00:00:00 2001 From: coeff-aij <175928954+coeff-aij@users.noreply.github.com> Date: Sat, 23 May 2026 17:46:56 +0900 Subject: [PATCH 001/142] add: ForallSort --- src/chc.rs | 61 ++++++++++++++++++++++++++++++++++++++- src/chc/format_context.rs | 1 + src/chc/unbox.rs | 5 ++++ src/rty.rs | 6 +++- 4 files changed, 71 insertions(+), 2 deletions(-) diff --git a/src/chc.rs b/src/chc.rs index 58166391..3d32d7c4 100644 --- a/src/chc.rs +++ b/src/chc.rs @@ -84,6 +84,45 @@ impl DatatypeSort { } } +rustc_index::newtype_index! { + /// An index representing sort-level variable. + /// + /// We manage sort-level variables using indices that are unique in the whole CHC system. + /// [`System`] contains `Vec` that manages the indices of the variables. + #[orderable] + #[debug_format = "a{}"] + pub struct ForallSortIdx { } +} + +impl Default for ForallSortIdx { + fn default() -> Self { + 0_usize.into() + } +} + +impl std::fmt::Display for ForallSortIdx { + fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result { + write!(f, "a{}", self.index()) + } +} + +impl<'a, D> Pretty<'a, D, termcolor::ColorSpec> for &ForallSortIdx +where + D: pretty::DocAllocator<'a, termcolor::ColorSpec>, +{ + fn pretty(self, allocator: &'a D) -> pretty::DocBuilder<'a, D, termcolor::ColorSpec> { + allocator + .as_string(self) + .annotate(ForallSortIdx::color_spec()) + } +} + +impl ForallSortIdx { + fn color_spec() -> termcolor::ColorSpec { + termcolor::ColorSpec::new() + } +} + /// A sort is the type of a logical term. #[derive(Debug, Clone, PartialEq, Eq, Hash, PartialOrd, Ord)] pub enum Sort { @@ -97,6 +136,7 @@ pub enum Sort { Tuple(Vec), Array(Box, Box), Datatype(DatatypeSort), + Forall(ForallSortIdx), } impl From for Sort { @@ -154,6 +194,7 @@ where } } Sort::Datatype(sort) => sort.pretty(allocator), + Sort::Forall(idx) => idx.pretty(allocator), } } } @@ -180,7 +221,12 @@ impl Sort { fn walk_impl<'a, 'b>(&'a self, mut f: Box) { f(self); match self { - Sort::Null | Sort::Int | Sort::Bool | Sort::String | Sort::Param(_) => {} + Sort::Null + | Sort::Int + | Sort::Bool + | Sort::String + | Sort::Param(_) + | Sort::Forall(_) => {} Sort::Box(s) | Sort::Mut(s) => s.walk(Box::new(&mut f)), Sort::Tuple(ss) => { for s in ss { @@ -261,6 +307,10 @@ impl Sort { Sort::Datatype(DatatypeSort { symbol, args }) } + pub fn forall(index: ForallSortIdx) -> Self { + Sort::Forall(index) + } + pub fn into_datatype(self) -> Option { match self { Sort::Datatype(sort) => Some(sort), @@ -1783,6 +1833,8 @@ pub struct System { pub user_defined_pred_defs: Vec, pub clauses: IndexVec, pub pred_vars: IndexVec, + pub forall_sorts: Vec, + pub num_forall_sort_idx: ForallSortIdx, } impl System { @@ -1790,6 +1842,13 @@ impl System { self.pred_vars.push(PredVarDef { sig, debug_info }) } + pub fn new_forall_sort(&mut self) -> ForallSortIdx { + let new_idx = self.num_forall_sort_idx; + self.num_forall_sort_idx += 1; + self.forall_sorts.push(new_idx); + new_idx + } + pub fn push_raw_command(&mut self, raw_command: RawCommand) { self.raw_commands.push(raw_command) } diff --git a/src/chc/format_context.rs b/src/chc/format_context.rs index 94548274..86895e02 100644 --- a/src/chc/format_context.rs +++ b/src/chc/format_context.rs @@ -87,6 +87,7 @@ impl<'a> std::fmt::Display for SortSymbol<'a> { write!(f, "Array{}", SortSymbols::new(&[*s1.clone(), *s2.clone()])) } chc::Sort::Datatype(s) => write!(f, "{}{}", s.symbol, SortSymbols::new(&s.args)), + chc::Sort::Forall(i) => write!(f, "{}", i), } } } diff --git a/src/chc/unbox.rs b/src/chc/unbox.rs index b3b24d52..08d36c4e 100644 --- a/src/chc/unbox.rs +++ b/src/chc/unbox.rs @@ -72,6 +72,7 @@ fn unbox_sort(sort: Sort) -> Sort { Sort::Tuple(sorts) => Sort::Tuple(sorts.into_iter().map(unbox_sort).collect()), Sort::Array(s1, s2) => Sort::Array(Box::new(unbox_sort(*s1)), Box::new(unbox_sort(*s2))), Sort::Datatype(sort) => Sort::Datatype(unbox_datatype_sort(sort)), + Sort::Forall(i) => Sort::Forall(i), } } @@ -174,6 +175,8 @@ pub fn unbox(system: System) -> System { user_defined_pred_defs, clauses, pred_vars, + forall_sorts, + num_forall_sort_idx, } = system; let datatypes = datatypes.into_iter().map(unbox_datatype).collect(); let clauses = clauses.into_iter().map(unbox_clause).collect(); @@ -188,5 +191,7 @@ pub fn unbox(system: System) -> System { user_defined_pred_defs, clauses, pred_vars, + forall_sorts, + num_forall_sort_idx, } } diff --git a/src/rty.rs b/src/rty.rs index f1254fc6..876dc02d 100644 --- a/src/rty.rs +++ b/src/rty.rs @@ -1757,7 +1757,11 @@ impl RefinedType { /// Substitutes type parameters in a sort. fn subst_ty_params_in_sort(sort: &mut chc::Sort, subst: &TypeParamSubst) { match sort { - chc::Sort::Null | chc::Sort::Int | chc::Sort::Bool | chc::Sort::String => {} + chc::Sort::Null + | chc::Sort::Int + | chc::Sort::Bool + | chc::Sort::String + | chc::Sort::Forall(_) => {} chc::Sort::Param(idx) => { let type_param_idx = TypeParamIdx::from_usize(*idx); if let Some(rty) = subst.get(type_param_idx) { From 2e330a33808828905fd51aa2586f0a4c617454a4 Mon Sep 17 00:00:00 2001 From: coeff-aij <175928954+coeff-aij@users.noreply.github.com> Date: Sun, 24 May 2026 17:54:43 +0900 Subject: [PATCH 002/142] change: translate param type using ForallSortIdx --- src/refine/template.rs | 46 +++++++++++++++++++----------------------- 1 file changed, 21 insertions(+), 25 deletions(-) diff --git a/src/refine/template.rs b/src/refine/template.rs index ed0762ed..56fca463 100644 --- a/src/refine/template.rs +++ b/src/refine/template.rs @@ -1,4 +1,6 @@ +use std::cell::RefCell; use std::collections::HashMap; +use std::rc::Rc; use rustc_index::IndexVec; use rustc_middle::mir::{Local, Mutability}; @@ -6,7 +8,7 @@ use rustc_middle::ty as mir_ty; use rustc_span::def_id::DefId; use super::basic_block::BasicBlockType; -use crate::analyze::DefIdCache; +use crate::analyze::{DefIdCache, TypeParams}; use crate::chc; use crate::refine; use crate::rty; @@ -71,43 +73,37 @@ where pub struct TypeBuilder<'tcx> { tcx: mir_ty::TyCtxt<'tcx>, def_ids: DefIdCache<'tcx>, + def_id: DefId, typing_env: mir_ty::TypingEnv<'tcx>, - /// Maps index in [`mir_ty::ParamTy`] to [`rty::TypeParamIdx`]. - /// These indices may differ because we skip lifetime parameters and they always need to be - /// mapped when we translate a [`mir_ty::ParamTy`] to [`rty::ParamType`]. - /// See [`rty::TypeParamIdx`] for more details. - param_idx_mapping: HashMap, + type_params: Rc>, + system: Rc>, } impl<'tcx> TypeBuilder<'tcx> { - pub fn new(tcx: mir_ty::TyCtxt<'tcx>, def_ids: DefIdCache<'tcx>, def_id: DefId) -> Self { - let generics = tcx.generics_of(def_id); - let mut param_idx_mapping: HashMap = Default::default(); - for i in 0..generics.count() { - let generic_param = generics.param_at(i, tcx); - match generic_param.kind { - mir_ty::GenericParamDefKind::Lifetime => {} - mir_ty::GenericParamDefKind::Type { .. } => { - param_idx_mapping.insert(i as u32, param_idx_mapping.len().into()); - } - mir_ty::GenericParamDefKind::Const { .. } => {} - } - } + pub fn new( + tcx: mir_ty::TyCtxt<'tcx>, + def_ids: DefIdCache<'tcx>, + def_id: DefId, + type_params: Rc>, + system: Rc>, + ) -> Self { let typing_env = mir_ty::TypingEnv::post_analysis(tcx, def_id); Self { tcx, def_ids, + def_id, typing_env, - param_idx_mapping, + type_params, + system, } } fn translate_param_type(&self, ty: &mir_ty::ParamTy) -> rty::Type { - let index = *self - .param_idx_mapping - .get(&ty.index) - .expect("unknown type param idx"); - rty::ParamType::new(index).into() + let mut type_params = self.type_params.borrow_mut(); + let index = type_params + .entry((self.def_id, ty.index)) + .or_insert(self.system.borrow_mut().new_forall_sort()); + rty::ParamType::new(*index).into() } /// Replaces {closure} types with thrust_models::Closure<{closure}>. From f95cfdee2c69f4c25feb082ea5d8273daca2c2fa Mon Sep 17 00:00:00 2001 From: coeff-aij <175928954+coeff-aij@users.noreply.github.com> Date: Mon, 8 Jun 2026 11:58:38 +0900 Subject: [PATCH 003/142] change: translate Type::Param into chc::Sort::Forall --- src/analyze.rs | 27 ++++++++++-- src/analyze/annot_fn.rs | 12 ++++- src/analyze/basic_block.rs | 2 +- src/analyze/local_def.rs | 2 +- src/rty.rs | 4 +- src/rty/params.rs | 89 +++++++++++++++++++------------------- 6 files changed, 83 insertions(+), 53 deletions(-) diff --git a/src/analyze.rs b/src/analyze.rs index 2f0dbe5d..5148279c 100644 --- a/src/analyze.rs +++ b/src/analyze.rs @@ -197,6 +197,7 @@ impl refine::EnumDefProvider for Rc> { } pub type Env = refine::Env>>; +pub type TypeParams = HashMap<(DefId, u32), chc::ForallSortIdx>; #[derive(Debug, Clone)] struct DeferredFormulaFnDef<'tcx> { @@ -224,6 +225,8 @@ pub struct Analyzer<'tcx> { def_ids: did_cache::DefIdCache<'tcx>, enum_defs: Rc>, + + type_params: Rc>, } impl<'tcx> crate::refine::TemplateRegistry for Analyzer<'tcx> { @@ -251,6 +254,7 @@ impl<'tcx> Analyzer<'tcx> { let system = Default::default(); let basic_blocks = Default::default(); let enum_defs = Default::default(); + let type_params = Default::default(); Self { tcx, defs, @@ -259,6 +263,7 @@ impl<'tcx> Analyzer<'tcx> { basic_blocks, def_ids: did_cache::DefIdCache::new(tcx), enum_defs, + type_params, } } @@ -292,7 +297,7 @@ impl<'tcx> Analyzer<'tcx> { .iter() .map(|field| { let field_ty = self.tcx.type_of(field.did).instantiate_identity(); - TypeBuilder::new(self.tcx, self.def_ids(), def_id).build(field_ty) + self.type_builder(self.def_ids(), def_id).build(field_ty) }) .collect(); rty::EnumVariantDef { @@ -412,7 +417,13 @@ impl<'tcx> Analyzer<'tcx> { def_id: DefId, generic_args: mir_ty::GenericArgsRef<'tcx>, ) -> Option { - let type_builder = TypeBuilder::new(self.tcx, self.def_ids(), def_id); + let type_builder = TypeBuilder::new( + self.tcx, + self.def_ids(), + def_id, + self.type_params.clone(), + self.system.clone(), + ); let mut def_ty = match self.defs.get(&def_id)? { DefTy::Concrete(rty) => rty.clone(), DefTy::Deferred(deferred) => deferred.cache.borrow().get(&generic_args)?.clone(), @@ -456,7 +467,7 @@ impl<'tcx> Analyzer<'tcx> { def_id: DefId, generic_args: mir_ty::GenericArgsRef<'tcx>, ) -> Option { - let type_builder = TypeBuilder::new(self.tcx, self.def_ids(), def_id); + let type_builder = self.type_builder(self.def_ids(), def_id); let deferred_ty = match self.defs.get(&def_id)? { DefTy::Concrete(rty) => { @@ -641,6 +652,16 @@ impl<'tcx> Analyzer<'tcx> { basic_block::Analyzer::new(self, local_def_id, bb) } + pub fn type_builder(&self, def_ids: DefIdCache<'tcx>, def_id: DefId) -> TypeBuilder<'tcx> { + TypeBuilder::new( + self.tcx, + def_ids, + def_id, + self.type_params.clone(), + self.system.clone(), + ) + } + pub fn solve(&mut self) { if let Err(err) = self.system.borrow().solve() { self.tcx.dcx().err(format!("verification error: {:?}", err)); diff --git a/src/analyze/annot_fn.rs b/src/analyze/annot_fn.rs index 2711cd81..2bbbe673 100644 --- a/src/analyze/annot_fn.rs +++ b/src/analyze/annot_fn.rs @@ -7,7 +7,7 @@ use rustc_middle::ty::{self as mir_ty, TyCtxt}; use crate::analyze::{self, did_cache::DefIdCache}; use crate::annot::AnnotFormula; -use crate::chc; +use crate::chc::{self}; use crate::refine::{self, TypeBuilder}; use crate::rty; @@ -151,7 +151,13 @@ impl<'a, 'tcx> AnnotFnTranslator<'a, 'tcx> { let generic_args = tcx.mk_args(&[]); let typeck = tcx.typeck(local_def_id); let def_ids = analyzer.def_ids(); - let type_builder = TypeBuilder::new(tcx, def_ids.clone(), local_def_id.to_def_id()); + let type_builder = TypeBuilder::new( + tcx, + def_ids.clone(), + local_def_id.to_def_id(), + analyzer.type_params.clone(), + analyzer.system.clone(), + ); let mut translator = Self { tcx, local_def_id, @@ -178,6 +184,8 @@ impl<'a, 'tcx> AnnotFnTranslator<'a, 'tcx> { self.tcx, self.def_ids.clone(), self.local_def_id.to_def_id(), + self.analyzer.type_params.clone(), + self.analyzer.system.clone(), ); self } diff --git a/src/analyze/basic_block.rs b/src/analyze/basic_block.rs index b76fb1a3..b7746f31 100644 --- a/src/analyze/basic_block.rs +++ b/src/analyze/basic_block.rs @@ -1334,7 +1334,7 @@ impl<'tcx, 'ctx> Analyzer<'tcx, 'ctx> { let env = ctx.new_env(); let local_decls = body.local_decls.clone(); let prophecy_vars = Default::default(); - let type_builder = TypeBuilder::new(tcx, ctx.def_ids(), local_def_id.to_def_id()); + let type_builder = ctx.type_builder(ctx.def_ids(), local_def_id.to_def_id()); Self { ctx, tcx, diff --git a/src/analyze/local_def.rs b/src/analyze/local_def.rs index 393c80d8..3169ac03 100644 --- a/src/analyze/local_def.rs +++ b/src/analyze/local_def.rs @@ -1185,7 +1185,7 @@ impl<'tcx, 'ctx> Analyzer<'tcx, 'ctx> { let tcx = ctx.tcx; let body = tcx.optimized_mir(local_def_id.to_def_id()).clone(); let drop_points = Default::default(); - let type_builder = TypeBuilder::new(tcx, ctx.def_ids(), local_def_id.to_def_id()); + let type_builder = ctx.type_builder(ctx.def_ids(), local_def_id.to_def_id()); let generic_args = tcx.mk_args(&[]); Self { ctx, diff --git a/src/rty.rs b/src/rty.rs index 876dc02d..8f6f930e 100644 --- a/src/rty.rs +++ b/src/rty.rs @@ -729,7 +729,7 @@ impl EnumType { /// A type parameter. #[derive(Debug, Clone)] pub struct ParamType { - pub idx: TypeParamIdx, + idx: TypeParamIdx, } impl<'a, D> Pretty<'a, D, termcolor::ColorSpec> for &ParamType @@ -1034,7 +1034,7 @@ impl Type { // currently String sort seems not available in HORN logic of Z3 Type::String => chc::Sort::null(), Type::Never => chc::Sort::null(), - Type::Param(ty) => chc::Sort::param(ty.index().into()), + Type::Param(ty) => chc::Sort::forall(ty.index()), Type::Pointer(ty) => { let elem_sort = ty.elem.ty.to_sort(); diff --git a/src/rty/params.rs b/src/rty/params.rs index ef05138e..88e0bf4e 100644 --- a/src/rty/params.rs +++ b/src/rty/params.rs @@ -2,56 +2,57 @@ use std::collections::BTreeMap; -use pretty::{termcolor, Pretty}; +// use pretty::{termcolor, Pretty}; use rustc_index::IndexVec; use crate::chc; use super::{Closed, RefinedType, Type}; -rustc_index::newtype_index! { - /// An index representing a type parameter. - /// - /// ## Note on indexing of type parameters - /// - /// The index of [`rustc_middle::ty::ParamTy`] is based on all generic parameters in - /// the definition, including lifetimes. Given the following definition: - /// - /// ```rust - /// struct X<'a, T> { f: &'a T } - /// ``` - /// - /// The type of field `f` is `&T1` (not `&T0`) in MIR. However, in Thrust, we ignore lifetime - /// parameters and the index of [`rty::ParamType`](super::ParamType) is based on type parameters only, giving `f` - /// the type `&T0`. [`TypeBuilder`](crate::refine::TypeBuilder) takes care of this difference when translating MIR - /// types to Thrust types. - #[orderable] - #[debug_format = "T{}"] - pub struct TypeParamIdx { } -} - -impl std::fmt::Display for TypeParamIdx { - fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result { - write!(f, "T{}", self.index()) - } -} - -impl<'a, D> Pretty<'a, D, termcolor::ColorSpec> for &TypeParamIdx -where - D: pretty::DocAllocator<'a, termcolor::ColorSpec>, -{ - fn pretty(self, allocator: &'a D) -> pretty::DocBuilder<'a, D, termcolor::ColorSpec> { - allocator - .as_string(self) - .annotate(TypeParamIdx::color_spec()) - } -} - -impl TypeParamIdx { - fn color_spec() -> termcolor::ColorSpec { - termcolor::ColorSpec::new() - } -} +pub type TypeParamIdx = chc::ForallSortIdx; +// rustc_index::newtype_index! { +// /// An index representing a type parameter. +// /// +// /// ## Note on indexing of type parameters +// /// +// /// The index of [`rustc_middle::ty::ParamTy`] is based on all generic parameters in +// /// the definition, including lifetimes. Given the following definition: +// /// +// /// ```rust +// /// struct X<'a, T> { f: &'a T } +// /// ``` +// /// +// /// The type of field `f` is `&T1` (not `&T0`) in MIR. However, in Thrust, we ignore lifetime +// /// parameters and the index of [`rty::ParamType`](super::ParamType) is based on type parameters only, giving `f` +// /// the type `&T0`. [`TypeBuilder`](crate::refine::TypeBuilder) takes care of this difference when translating MIR +// /// types to Thrust types. +// #[orderable] +// #[debug_format = "T{}"] +// pub struct TypeParamIdx { } +// } + +// impl std::fmt::Display for TypeParamIdx { +// fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result { +// write!(f, "T{}", self.index()) +// } +// } + +// impl<'a, D> Pretty<'a, D, termcolor::ColorSpec> for &TypeParamIdx +// where +// D: pretty::DocAllocator<'a, termcolor::ColorSpec>, +// { +// fn pretty(self, allocator: &'a D) -> pretty::DocBuilder<'a, D, termcolor::ColorSpec> { +// allocator +// .as_string(self) +// .annotate(TypeParamIdx::color_spec()) +// } +// } + +// impl TypeParamIdx { +// fn color_spec() -> termcolor::ColorSpec { +// termcolor::ColorSpec::new() +// } +// } pub type RefinedTypeArgs = IndexVec>; pub type TypeArgs = IndexVec>; From debaa614289ab1185f025dc448599728620ac022 Mon Sep 17 00:00:00 2001 From: coeff-aij <175928954+coeff-aij@users.noreply.github.com> Date: Mon, 8 Jun 2026 12:00:48 +0900 Subject: [PATCH 004/142] change: use forall sort instead of deferred type --- src/analyze/crate_.rs | 13 +++---------- 1 file changed, 3 insertions(+), 10 deletions(-) diff --git a/src/analyze/crate_.rs b/src/analyze/crate_.rs index 74198f18..d5ed1f95 100644 --- a/src/analyze/crate_.rs +++ b/src/analyze/crate_.rs @@ -120,16 +120,9 @@ impl<'tcx, 'ctx> Analyzer<'tcx, 'ctx> { }; use mir_ty::TypeVisitableExt as _; - if sig.has_param() { - // TODO: needs clear criteria on whether extern_spec'ed target fn is analyzed or not - if target_def_id.as_local().is_none_or(|def_id| { - self.skip_analysis.contains(&def_id) || !self.tcx.is_mir_available(def_id) - }) { - self.ctx - .register_deferred_def_without_analysis(target_def_id, local_def_id); - } else { - self.ctx.register_deferred_def(target_def_id, local_def_id); - } + if sig.has_param() && self.skip_analysis.contains(&local_def_id) { + self.ctx + .register_deferred_def_without_analysis(target_def_id, local_def_id); } else { let expected = analyzer.expected_ty(); self.ctx.register_def(target_def_id, expected); From 03d79322e23315923055ea664ef09a3dad0d0350 Mon Sep 17 00:00:00 2001 From: coeff-aij <175928954+coeff-aij@users.noreply.github.com> Date: Sun, 24 May 2026 21:49:11 +0900 Subject: [PATCH 005/142] add: output (define-forall-sort) --- src/chc/smtlib2.rs | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/chc/smtlib2.rs b/src/chc/smtlib2.rs index e8886ed6..617c1ac7 100644 --- a/src/chc/smtlib2.rs +++ b/src/chc/smtlib2.rs @@ -600,6 +600,10 @@ impl<'a> std::fmt::Display for System<'a> { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { writeln!(f, "(set-logic HORN)\n")?; + for forall_sort_idx in &self.inner.forall_sorts { + writeln!(f, "(declare-forall-sort {})\n", forall_sort_idx)?; + } + writeln!(f, "{}\n", Datatypes::new(&self.ctx, self.ctx.datatypes()))?; for datatype in self.ctx.datatypes() { writeln!(f, "{}", DatatypeDiscrFun::new(&self.ctx, datatype))?; From 0d47cc8377c266fe79ef8ea39cb2c2556827d386 Mon Sep 17 00:00:00 2001 From: coeff-aij <175928954+coeff-aij@users.noreply.github.com> Date: Sun, 24 May 2026 21:49:33 +0900 Subject: [PATCH 006/142] fix: duplication of ForallSortIdx for the same parameter --- src/refine/template.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/refine/template.rs b/src/refine/template.rs index 56fca463..37aa9a67 100644 --- a/src/refine/template.rs +++ b/src/refine/template.rs @@ -102,7 +102,7 @@ impl<'tcx> TypeBuilder<'tcx> { let mut type_params = self.type_params.borrow_mut(); let index = type_params .entry((self.def_id, ty.index)) - .or_insert(self.system.borrow_mut().new_forall_sort()); + .or_insert_with(|| self.system.borrow_mut().new_forall_sort()); rty::ParamType::new(*index).into() } From 4e35b61e73be3fcd125f11e4f1e73afae9513524 Mon Sep 17 00:00:00 2001 From: coeff-aij <175928954+coeff-aij@users.noreply.github.com> Date: Sun, 24 May 2026 22:53:26 +0900 Subject: [PATCH 007/142] add test cases using unknown type parameters with trait bounds --- tests/ui/pass/traits/simple_loop.rs | 28 ++++++++++++++++++++++++++++ 1 file changed, 28 insertions(+) create mode 100644 tests/ui/pass/traits/simple_loop.rs diff --git a/tests/ui/pass/traits/simple_loop.rs b/tests/ui/pass/traits/simple_loop.rs new file mode 100644 index 00000000..857ecdb3 --- /dev/null +++ b/tests/ui/pass/traits/simple_loop.rs @@ -0,0 +1,28 @@ +//@check-pass +//@compile-flags: -C debug-assertions=off +//@rustc-env: THRUST_SOLVER=tests/thrust-pcsat-wrapper THRUST_SOLVER_TIMEOUT_SECS=60 + +#[thrust_macros::context] +trait A { + #[thrust_macros::requires(Self::p(x))] + #[thrust_macros::ensures(Self::p(result))] + fn f(&self, x: i64) -> i64; + + #[thrust_macros::predicate] + fn p(x: i64) -> bool; +} + +#[thrust_macros::requires(T::p(x))] +#[thrust_macros::ensures(T::p(result))] +fn target(a: &T, x: i64) -> i64 { + let mut v = x; + let mut i = 0; + while i < 3 { + v = a.f(v); + i += 1; + } + + v +} + +fn main() {} From 2ef31cbcbb38c2c635adff3e73cdb71cdb5ca690 Mon Sep 17 00:00:00 2001 From: coeff-aij <175928954+coeff-aij@users.noreply.github.com> Date: Mon, 25 May 2026 22:19:14 +0900 Subject: [PATCH 008/142] change: use DeferredType for generic functions without requires/ensures conditions --- src/analyze/crate_.rs | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/analyze/crate_.rs b/src/analyze/crate_.rs index d5ed1f95..78114384 100644 --- a/src/analyze/crate_.rs +++ b/src/analyze/crate_.rs @@ -123,6 +123,9 @@ impl<'tcx, 'ctx> Analyzer<'tcx, 'ctx> { if sig.has_param() && self.skip_analysis.contains(&local_def_id) { self.ctx .register_deferred_def_without_analysis(target_def_id, local_def_id); + } else if sig.has_param() && !analyzer.is_fully_annotated() { + self.ctx + .register_deferred_def(local_def_id); } else { let expected = analyzer.expected_ty(); self.ctx.register_def(target_def_id, expected); From f42debfbfbfc7d86f8045fe4c2f8c84eb6dc3e1f Mon Sep 17 00:00:00 2001 From: coeff-aij <175928954+coeff-aij@users.noreply.github.com> Date: Mon, 25 May 2026 22:53:52 +0900 Subject: [PATCH 009/142] change: prevent overwriting concrete types with deferred types --- src/analyze.rs | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/src/analyze.rs b/src/analyze.rs index 5148279c..a770c605 100644 --- a/src/analyze.rs +++ b/src/analyze.rs @@ -392,9 +392,8 @@ impl<'tcx> Analyzer<'tcx> { ?mode, "register_deferred_def" ); - self.defs.insert( - target_def_id, - DefTy::Deferred(DeferredDefTy { + self.defs.entry( target_def_id).or_insert_with( + || DefTy::Deferred(DeferredDefTy { local_def_id, cache: Rc::new(RefCell::new(HashMap::new())), mode, From 0a2cc1772c22e2a9f775b681c97fbab6d5be6698 Mon Sep 17 00:00:00 2001 From: coeff-aij <175928954+coeff-aij@users.noreply.github.com> Date: Tue, 26 May 2026 00:35:02 +0900 Subject: [PATCH 010/142] change: disable DeferredType completely --- src/analyze/crate_.rs | 16 +++++----------- 1 file changed, 5 insertions(+), 11 deletions(-) diff --git a/src/analyze/crate_.rs b/src/analyze/crate_.rs index 78114384..155e0bec 100644 --- a/src/analyze/crate_.rs +++ b/src/analyze/crate_.rs @@ -119,17 +119,8 @@ impl<'tcx, 'ctx> Analyzer<'tcx, 'ctx> { local_def_id.to_def_id() }; - use mir_ty::TypeVisitableExt as _; - if sig.has_param() && self.skip_analysis.contains(&local_def_id) { - self.ctx - .register_deferred_def_without_analysis(target_def_id, local_def_id); - } else if sig.has_param() && !analyzer.is_fully_annotated() { - self.ctx - .register_deferred_def(local_def_id); - } else { - let expected = analyzer.expected_ty(); - self.ctx.register_def(target_def_id, expected); - } + let expected = analyzer.expected_ty(); + self.ctx.register_def(target_def_id, expected); } fn analyze_local_defs(&mut self) { @@ -138,11 +129,13 @@ impl<'tcx, 'ctx> Analyzer<'tcx, 'ctx> { continue; }; if self.skip_analysis.contains(local_def_id) { + tracing::debug!("this is marked as skip analysis: {:?}", local_def_id); continue; } let Some(expected) = self.ctx.concrete_def_ty(local_def_id.to_def_id()) else { // when the local_def_id is deferred it would be skipped + tracing::debug!("this is marked as deferred type: {:?}", local_def_id); continue; }; @@ -156,6 +149,7 @@ impl<'tcx, 'ctx> Analyzer<'tcx, 'ctx> { .map(|ty_param| (ty_param, rty::RefinedType::unrefined(rty::Type::int()))) .collect(), ); + tracing::debug!("expected type of {:?} is {:#?}", local_def_id, expected); expected.subst_ty_params(&subst); let generic_args = self.placeholder_generic_args(*local_def_id); self.ctx From dc9b0e91334b4b7622bfbef8045d0838970ef8cd Mon Sep 17 00:00:00 2001 From: coeff-aij <175928954+coeff-aij@users.noreply.github.com> Date: Mon, 8 Jun 2026 12:04:25 +0900 Subject: [PATCH 011/142] remove all extern_spec in std.rs temporarily --- src/analyze.rs | 8 +- src/analyze/crate_.rs | 2 - std.rs | 814 +++++++++++++++++++++--------------------- 3 files changed, 411 insertions(+), 413 deletions(-) diff --git a/src/analyze.rs b/src/analyze.rs index a770c605..2545baff 100644 --- a/src/analyze.rs +++ b/src/analyze.rs @@ -392,13 +392,13 @@ impl<'tcx> Analyzer<'tcx> { ?mode, "register_deferred_def" ); - self.defs.entry( target_def_id).or_insert_with( - || DefTy::Deferred(DeferredDefTy { + self.defs.entry(target_def_id).or_insert_with(|| { + DefTy::Deferred(DeferredDefTy { local_def_id, cache: Rc::new(RefCell::new(HashMap::new())), mode, - }), - ); + }) + }); } pub fn concrete_def_ty(&self, def_id: DefId) -> Option<&rty::RefinedType> { diff --git a/src/analyze/crate_.rs b/src/analyze/crate_.rs index 155e0bec..fb7a01b3 100644 --- a/src/analyze/crate_.rs +++ b/src/analyze/crate_.rs @@ -69,8 +69,6 @@ impl<'tcx, 'ctx> Analyzer<'tcx, 'ctx> { #[tracing::instrument(skip(self), fields(def_id = %self.tcx.def_path_str(local_def_id)))] fn refine_fn_def(&mut self, local_def_id: LocalDefId) { - let sig = self.ctx.fn_sig(local_def_id.to_def_id()); - let mut analyzer = self.ctx.local_def_analyzer(local_def_id); if analyzer.is_annotated_as_trusted() { diff --git a/std.rs b/std.rs index 132e3859..56aa2d06 100644 --- a/std.rs +++ b/std.rs @@ -333,410 +333,410 @@ mod thrust_models { } } -#[thrust::extern_spec_fn] -#[thrust_macros::requires(true)] -#[thrust_macros::ensures(result == thrust_models::model::Box::new(x))] -fn _extern_spec_box_new(x: T) -> Box where T: thrust_models::Model, T::Ty: PartialEq { - Box::new(x) -} - -#[thrust::extern_spec_fn] -#[thrust_macros::requires(true)] -#[thrust_macros::ensures(result == (x == y))] -fn _extern_spec_box_partialeq_eq(x: &Box, y: &Box) -> bool - where T: thrust_models::Model + PartialEq, T::Ty: PartialEq -{ - as PartialEq>::eq(x, y) -} - -#[thrust::extern_spec_fn] -#[thrust_macros::requires(true)] -#[thrust_macros::ensures(*x == !y && *y == !x)] -fn _extern_spec_std_mem_swap(x: &mut T, y: &mut T) where T: thrust_models::Model, T::Ty: PartialEq { - std::mem::swap(x, y) -} - -#[thrust::extern_spec_fn] -#[thrust_macros::requires(true)] -#[thrust_macros::ensures(!dest == src && result == *dest)] -fn _extern_spec_std_mem_replace(dest: &mut T, src: T) -> T where T: thrust_models::Model, T::Ty: PartialEq { - std::mem::replace(dest, src) -} - -#[thrust::extern_spec_fn] -#[thrust_macros::requires(true)] -#[thrust_macros::ensures(result == (x == y))] -fn _extern_spec_option_partialeq_eq(x: &Option, y: &Option) -> bool - where T: thrust_models::Model + PartialEq, T::Ty: PartialEq -{ - as PartialEq>::eq(x, y) -} - -#[thrust::extern_spec_fn] -#[thrust_macros::requires(opt != None)] -#[thrust_macros::ensures(Some(result) == opt)] -fn _extern_spec_option_unwrap(opt: Option) -> T where T: thrust_models::Model, T::Ty: PartialEq { - Option::unwrap(opt) -} - -#[thrust::extern_spec_fn] -#[thrust_macros::requires(true)] -#[thrust_macros::ensures( - (*opt == None && result == true) - || (*opt != None && result == false) -)] -fn _extern_spec_option_is_none(opt: &Option) -> bool where T: thrust_models::Model, T::Ty: PartialEq { - Option::is_none(opt) -} - -#[thrust::extern_spec_fn] -#[thrust_macros::requires(true)] -#[thrust_macros::ensures( - (*opt == None && result == false) - || (*opt != None && result == true) -)] -fn _extern_spec_option_is_some(opt: &Option) -> bool where T: thrust_models::Model, T::Ty: PartialEq { - Option::is_some(opt) -} - -#[thrust::extern_spec_fn] -#[thrust_macros::requires(true)] -#[thrust_macros::ensures( - (opt != None && Some(result) == opt) - || (opt == None && result == default) -)] -fn _extern_spec_option_unwrap_or(opt: Option, default: T) -> T where T: thrust_models::Model, T::Ty: PartialEq { - Option::unwrap_or(opt, default) -} - -#[thrust::extern_spec_fn] -#[thrust_macros::requires( - opt == None || thrust_models::exists(|i| opt == Some(i) && thrust_macros::pre!(f(i))) -)] -#[thrust_macros::ensures( - (opt == None && result == None) - || thrust_models::exists(|i| thrust_models::exists(|j| - opt == Some(i) && thrust_macros::post!(f(i), j) && result == Some(j))) -)] -fn _extern_spec_option_map(opt: Option, f: F) -> Option -where - T: thrust_models::Model, T::Ty: PartialEq, - U: thrust_models::Model, U::Ty: PartialEq, - F: FnOnce(T) -> U, -{ - Option::map(opt, f) -} - -#[thrust::extern_spec_fn] -#[thrust_macros::requires(opt != None || thrust_macros::pre!(f()))] -#[thrust_macros::ensures( - (opt != None && Some(result) == opt) - || (opt == None && thrust_macros::post!(f(), result)) -)] -fn _extern_spec_option_unwrap_or_else(opt: Option, f: F) -> T -where - T: thrust_models::Model, T::Ty: PartialEq, - F: FnOnce() -> T, -{ - Option::unwrap_or_else(opt, f) -} - -#[thrust::extern_spec_fn] -#[thrust_macros::requires(true)] -#[thrust_macros::ensures( - (thrust_models::exists(|x| opt == Some(x) && result == Ok(x))) - || (opt == None && result == Err(err)) -)] -fn _extern_spec_option_ok_or(opt: Option, err: E) -> Result - where T: thrust_models::Model, T::Ty: PartialEq, - E: thrust_models::Model, E::Ty: PartialEq, -{ - Option::ok_or(opt, err) -} - -#[thrust::extern_spec_fn] -#[thrust_macros::requires(true)] -#[thrust_macros::ensures(!opt == None && result == *opt)] -fn _extern_spec_option_take(opt: &mut Option) -> Option where T: thrust_models::Model, T::Ty: PartialEq { - Option::take(opt) -} - -#[thrust::extern_spec_fn] -#[thrust_macros::requires(true)] -#[thrust_macros::ensures(!opt == Some(src) && result == *opt)] -fn _extern_spec_option_replace(opt: &mut Option, src: T) -> Option - where T: thrust_models::Model, T::Ty: PartialEq -{ - Option::replace(opt, src) -} - -#[thrust::extern_spec_fn] -#[thrust_macros::requires(true)] -#[thrust_macros::ensures( - thrust_models::exists(|x| opt == &Some(x) && result == Some(&x)) - || (opt == &None && result == None) -)] -fn _extern_spec_option_as_ref(opt: &Option) -> Option<&T> where T: thrust_models::Model, T::Ty: PartialEq { - Option::as_ref(opt) -} - -#[thrust::extern_spec_fn] -#[thrust_macros::requires(true)] -#[thrust_macros::ensures( - thrust_models::exists(|x1, x2| - *opt == Some(x1) && - !opt == Some(x2) && - result == Some(thrust_models::model::Mut::new(x1, x2)) - ) - || ( - *opt == None && - !opt == None && - result == None - ) -)] -fn _extern_spec_option_as_mut(opt: &mut Option) -> Option<&mut T> - where T: thrust_models::Model, T::Ty: PartialEq -{ - Option::as_mut(opt) -} - -#[thrust::extern_spec_fn] -#[thrust_macros::requires(true)] -#[thrust_macros::ensures(result == (x == y))] -fn _extern_spec_result_partialeq_eq(x: &Result, y: &Result) -> bool - where T: thrust_models::Model + PartialEq, T::Ty: PartialEq, - E: thrust_models::Model + PartialEq, E::Ty: PartialEq, -{ - as PartialEq>::eq(x, y) -} - -#[thrust::extern_spec_fn] -#[thrust_macros::requires(thrust_models::exists(|x| res == Ok(x)))] -#[thrust_macros::ensures(Ok(result) == res)] -fn _extern_spec_result_unwrap(res: Result) -> T - where T: thrust_models::Model, T::Ty: PartialEq, - E: thrust_models::Model, E::Ty: PartialEq, -{ - Result::unwrap(res) -} - -#[thrust::extern_spec_fn] -#[thrust_macros::requires(thrust_models::exists(|x| res == Err(x)))] -#[thrust_macros::ensures(Err(result) == res)] -fn _extern_spec_result_unwrap_err(res: Result) -> E - where T: thrust_models::Model, T::Ty: PartialEq, - E: thrust_models::Model, E::Ty: PartialEq, -{ - Result::unwrap_err(res) -} - -#[thrust::extern_spec_fn] -#[thrust_macros::requires(true)] -#[thrust_macros::ensures( - thrust_models::exists(|x| res == Ok(x) && result == Some(x)) - || thrust_models::exists(|x| res == Err(x) && result == None) -)] -fn _extern_spec_result_ok(res: Result) -> Option - where T: thrust_models::Model, T::Ty: PartialEq, - E: thrust_models::Model, E::Ty: PartialEq, -{ - Result::ok(res) -} - -#[thrust::extern_spec_fn] -#[thrust_macros::requires(true)] -#[thrust_macros::ensures( - thrust_models::exists(|x| res == Ok(x) && result == None) - || thrust_models::exists(|x| res == Err(x) && result == Some(x)) -)] -fn _extern_spec_result_err(res: Result) -> Option - where T: thrust_models::Model, T::Ty: PartialEq, - E: thrust_models::Model, E::Ty: PartialEq, -{ - Result::err(res) -} - -#[thrust::extern_spec_fn] -#[thrust_macros::requires(true)] -#[thrust_macros::ensures( - thrust_models::exists(|x| *res == Ok(x) && result == true) - || thrust_models::exists(|x| *res == Err(x) && result == false) -)] -fn _extern_spec_result_is_ok(res: &Result) -> bool - where T: thrust_models::Model, T::Ty: PartialEq, - E: thrust_models::Model, E::Ty: PartialEq, -{ - Result::is_ok(res) -} - -#[thrust::extern_spec_fn] -#[thrust_macros::requires(true)] -#[thrust_macros::ensures( - thrust_models::exists(|x| *res == Ok(x) && result == false) - || thrust_models::exists(|x| *res == Err(x) && result == true) -)] -fn _extern_spec_result_is_err(res: &Result) -> bool - where T: thrust_models::Model, T::Ty: PartialEq, - E: thrust_models::Model, E::Ty: PartialEq, -{ - Result::is_err(res) -} - -#[thrust::extern_spec_fn] -#[thrust_macros::requires(true)] // TODO: require x != i32::MIN -#[thrust_macros::ensures(result >= 0 && (result == x || result == -x))] -fn _extern_spec_i32_abs(x: i32) -> i32 { - i32::abs(x) -} - -#[thrust::extern_spec_fn] -#[thrust_macros::requires(true)] -#[thrust_macros::ensures( - (x >= y && result == (x - y)) - || (x < y && result == (y - x)) -)] -fn _extern_spec_i32_abs_diff(x: i32, y: i32) -> u32 { - i32::abs_diff(x, y) -} - -#[thrust::extern_spec_fn] -#[thrust_macros::requires(true)] -#[thrust_macros::ensures((x == 0 && result == 0) || (x > 0 && result == 1) || (x < 0 && result == -1))] -fn _extern_spec_i32_signum(x: i32) -> i32 { - i32::signum(x) -} - -#[thrust::extern_spec_fn] -#[thrust_macros::requires(true)] -#[thrust_macros::ensures((x < 0 && result == false) || (x >= 0 && result == true))] -fn _extern_spec_i32_is_positive(x: i32) -> bool { - i32::is_positive(x) -} - -#[thrust::extern_spec_fn] -#[thrust_macros::requires(true)] -#[thrust_macros::ensures((x <= 0 && result == true) || (x > 0 && result == false))] -fn _extern_spec_i32_is_negative(x: i32) -> bool { - i32::is_negative(x) -} - -#[thrust::extern_spec_fn] -#[thrust_macros::requires(true)] -#[thrust_macros::ensures(result.1 == 0)] -fn _extern_spec_vec_new() -> Vec where T: thrust_models::Model, T::Ty: PartialEq { - Vec::::new() -} - -#[thrust::extern_spec_fn] -#[thrust_macros::requires(true)] -#[thrust_macros::ensures(!vec == thrust_models::model::Vec((*vec).0.store((*vec).1, elem), (*vec).1 + 1))] -fn _extern_spec_vec_push(vec: &mut Vec, elem: T) - where T: thrust_models::Model, T::Ty: PartialEq -{ - Vec::push(vec, elem) -} - -#[thrust::extern_spec_fn] -#[thrust_macros::requires(true)] -#[thrust_macros::ensures(result == vec.1)] -fn _extern_spec_vec_len(vec: &Vec) -> usize where T: thrust_models::Model, T::Ty: PartialEq { - Vec::len(vec) -} - -#[thrust::extern_spec_fn] -#[thrust_macros::requires(index < vec.1)] -#[thrust_macros::ensures(*result == vec.0[index])] -fn _extern_spec_vec_index(vec: &Vec, index: usize) -> &T where T: thrust_models::Model, T::Ty: PartialEq { - as std::ops::Index>::index(vec, index) -} - -#[thrust::extern_spec_fn] -#[thrust_macros::requires(index < (*vec).1)] -#[thrust_macros::ensures( - *result == (*vec).0[index] && - !result == (!vec).0[index] && - !vec == thrust_models::model::Vec((*vec).0.store(index, !result), (*vec).1) -)] -fn _extern_spec_vec_index_mut(vec: &mut Vec, index: usize) -> &mut T - where T: thrust_models::Model, T::Ty: PartialEq -{ - as std::ops::IndexMut>::index_mut(vec, index) -} - -#[thrust::extern_spec_fn] -#[thrust_macros::requires(true)] -#[thrust_macros::ensures((!vec).1 == 0)] -fn _extern_spec_vec_clear(vec: &mut Vec) where T: thrust_models::Model, T::Ty: PartialEq { - Vec::clear(vec) -} - -#[thrust::extern_spec_fn] -#[thrust_macros::requires(true)] -#[thrust_macros::ensures( - (!vec).0 == (*vec).0 && ( - ( - (*vec).1 > 0 && - (!vec).1 == (*vec).1 - 1 && - result == Some((*vec).0[(*vec).1 - 1]) - ) || ( - (*vec).1 == 0 && - (!vec).1 == 0 && - result == None - ) - ) -)] -fn _extern_spec_vec_pop(vec: &mut Vec) -> Option where T: thrust_models::Model, T::Ty: PartialEq { - Vec::pop(vec) -} - -#[thrust::extern_spec_fn] -#[thrust_macros::requires(true)] -#[thrust_macros::ensures(result == ((*vec).1 == 0))] -fn _extern_spec_vec_is_empty(vec: &Vec) -> bool where T: thrust_models::Model, T::Ty: PartialEq { - Vec::is_empty(vec) -} - -#[thrust::extern_spec_fn] -#[thrust_macros::requires(true)] -#[thrust_macros::ensures( - ( - (*vec).1 > len && - !vec == thrust_models::model::Vec((*vec).0, len) - ) || ( - (*vec).1 <= len && - !vec == *vec - ) -)] -fn _extern_spec_vec_truncate(vec: &mut Vec, len: usize) where T: thrust_models::Model, T::Ty: PartialEq { - Vec::truncate(vec, len) -} - -// TODO: The following specs of some trait methods are too restrictive; we should allow for a -// per-impl spec once we can describe the spec of blanket impls. - -#[thrust::extern_spec_fn] -#[thrust_macros::requires(true)] -#[thrust_macros::ensures(result == (*x == *y))] -fn _extern_spec_partialeq_eq(x: &T, y: &T) -> bool - where T: thrust_models::Model + PartialEq, T::Ty: PartialEq -{ - PartialEq::eq(x, y) -} - -#[thrust::extern_spec_fn] -#[thrust_macros::requires(true)] -#[thrust_macros::ensures(result == (*x < *y))] -fn _extern_spec_partialord_lt(x: &T, y: &T) -> bool - where T: thrust_models::Model + PartialOrd, T::Ty: PartialOrd -{ - PartialOrd::lt(x, y) -} - -#[thrust::extern_spec_fn] -#[thrust_macros::requires(true)] -#[thrust_macros::ensures(result == (*x > *y))] -fn _extern_spec_partialord_gt(x: &T, y: &T) -> bool - where T: thrust_models::Model + PartialOrd, T::Ty: PartialOrd -{ - PartialOrd::gt(x, y) -} +// #[thrust::extern_spec_fn] +// #[thrust_macros::requires(true)] +// #[thrust_macros::ensures(result == thrust_models::model::Box::new(x))] +// fn _extern_spec_box_new(x: T) -> Box where T: thrust_models::Model, T::Ty: PartialEq { +// Box::new(x) +// } + +// #[thrust::extern_spec_fn] +// #[thrust_macros::requires(true)] +// #[thrust_macros::ensures(result == (x == y))] +// fn _extern_spec_box_partialeq_eq(x: &Box, y: &Box) -> bool +// where T: thrust_models::Model + PartialEq, T::Ty: PartialEq +// { +// as PartialEq>::eq(x, y) +// } + +// #[thrust::extern_spec_fn] +// #[thrust_macros::requires(true)] +// #[thrust_macros::ensures(*x == !y && *y == !x)] +// fn _extern_spec_std_mem_swap(x: &mut T, y: &mut T) where T: thrust_models::Model, T::Ty: PartialEq { +// std::mem::swap(x, y) +// } + +// #[thrust::extern_spec_fn] +// #[thrust_macros::requires(true)] +// #[thrust_macros::ensures(!dest == src && result == *dest)] +// fn _extern_spec_std_mem_replace(dest: &mut T, src: T) -> T where T: thrust_models::Model, T::Ty: PartialEq { +// std::mem::replace(dest, src) +// } + +// #[thrust::extern_spec_fn] +// #[thrust_macros::requires(true)] +// #[thrust_macros::ensures(result == (x == y))] +// fn _extern_spec_option_partialeq_eq(x: &Option, y: &Option) -> bool +// where T: thrust_models::Model + PartialEq, T::Ty: PartialEq +// { +// as PartialEq>::eq(x, y) +// } + +// #[thrust::extern_spec_fn] +// #[thrust_macros::requires(opt != None)] +// #[thrust_macros::ensures(Some(result) == opt)] +// fn _extern_spec_option_unwrap(opt: Option) -> T where T: thrust_models::Model, T::Ty: PartialEq { +// Option::unwrap(opt) +// } + +// #[thrust::extern_spec_fn] +// #[thrust_macros::requires(true)] +// #[thrust_macros::ensures( +// (*opt == None && result == true) +// || (*opt != None && result == false) +// )] +// fn _extern_spec_option_is_none(opt: &Option) -> bool where T: thrust_models::Model, T::Ty: PartialEq { +// Option::is_none(opt) +// } + +// #[thrust::extern_spec_fn] +// #[thrust_macros::requires(true)] +// #[thrust_macros::ensures( +// (*opt == None && result == false) +// || (*opt != None && result == true) +// )] +// fn _extern_spec_option_is_some(opt: &Option) -> bool where T: thrust_models::Model, T::Ty: PartialEq { +// Option::is_some(opt) +// } + +// #[thrust::extern_spec_fn] +// #[thrust_macros::requires(true)] +// #[thrust_macros::ensures( +// (opt != None && Some(result) == opt) +// || (opt == None && result == default) +// )] +// fn _extern_spec_option_unwrap_or(opt: Option, default: T) -> T where T: thrust_models::Model, T::Ty: PartialEq { +// Option::unwrap_or(opt, default) +// } + +// #[thrust::extern_spec_fn] +// #[thrust_macros::requires( +// opt == None || thrust_models::exists(|i| opt == Some(i) && thrust_macros::pre!(f(i))) +// )] +// #[thrust_macros::ensures( +// (opt == None && result == None) +// || thrust_models::exists(|i| thrust_models::exists(|j| +// opt == Some(i) && thrust_macros::post!(f(i), j) && result == Some(j))) +// )] +// fn _extern_spec_option_map(opt: Option, f: F) -> Option +// where +// T: thrust_models::Model, T::Ty: PartialEq, +// U: thrust_models::Model, U::Ty: PartialEq, +// F: FnOnce(T) -> U, +// { +// Option::map(opt, f) +// } + +// #[thrust::extern_spec_fn] +// #[thrust_macros::requires(opt != None || thrust_macros::pre!(f()))] +// #[thrust_macros::ensures( +// (opt != None && Some(result) == opt) +// || (opt == None && thrust_macros::post!(f(), result)) +// )] +// fn _extern_spec_option_unwrap_or_else(opt: Option, f: F) -> T +// where +// T: thrust_models::Model, T::Ty: PartialEq, +// F: FnOnce() -> T, +// { +// Option::unwrap_or_else(opt, f) +// } + +// #[thrust::extern_spec_fn] +// #[thrust_macros::requires(true)] +// #[thrust_macros::ensures( +// (thrust_models::exists(|x| opt == Some(x) && result == Ok(x))) +// || (opt == None && result == Err(err)) +// )] +// fn _extern_spec_option_ok_or(opt: Option, err: E) -> Result +// where T: thrust_models::Model, T::Ty: PartialEq, +// E: thrust_models::Model, E::Ty: PartialEq, +// { +// Option::ok_or(opt, err) +// } + +// #[thrust::extern_spec_fn] +// #[thrust_macros::requires(true)] +// #[thrust_macros::ensures(!opt == None && result == *opt)] +// fn _extern_spec_option_take(opt: &mut Option) -> Option where T: thrust_models::Model, T::Ty: PartialEq { +// Option::take(opt) +// } + +// #[thrust::extern_spec_fn] +// #[thrust_macros::requires(true)] +// #[thrust_macros::ensures(!opt == Some(src) && result == *opt)] +// fn _extern_spec_option_replace(opt: &mut Option, src: T) -> Option +// where T: thrust_models::Model, T::Ty: PartialEq +// { +// Option::replace(opt, src) +// } + +// #[thrust::extern_spec_fn] +// #[thrust_macros::requires(true)] +// #[thrust_macros::ensures( +// thrust_models::exists(|x| opt == &Some(x) && result == Some(&x)) +// || (opt == &None && result == None) +// )] +// fn _extern_spec_option_as_ref(opt: &Option) -> Option<&T> where T: thrust_models::Model, T::Ty: PartialEq { +// Option::as_ref(opt) +// } + +// #[thrust::extern_spec_fn] +// #[thrust_macros::requires(true)] +// #[thrust_macros::ensures( +// thrust_models::exists(|x1, x2| +// *opt == Some(x1) && +// !opt == Some(x2) && +// result == Some(thrust_models::model::Mut::new(x1, x2)) +// ) +// || ( +// *opt == None && +// !opt == None && +// result == None +// ) +// )] +// fn _extern_spec_option_as_mut(opt: &mut Option) -> Option<&mut T> +// where T: thrust_models::Model, T::Ty: PartialEq +// { +// Option::as_mut(opt) +// } + +// #[thrust::extern_spec_fn] +// #[thrust_macros::requires(true)] +// #[thrust_macros::ensures(result == (x == y))] +// fn _extern_spec_result_partialeq_eq(x: &Result, y: &Result) -> bool +// where T: thrust_models::Model + PartialEq, T::Ty: PartialEq, +// E: thrust_models::Model + PartialEq, E::Ty: PartialEq, +// { +// as PartialEq>::eq(x, y) +// } + +// #[thrust::extern_spec_fn] +// #[thrust_macros::requires(thrust_models::exists(|x| res == Ok(x)))] +// #[thrust_macros::ensures(Ok(result) == res)] +// fn _extern_spec_result_unwrap(res: Result) -> T +// where T: thrust_models::Model, T::Ty: PartialEq, +// E: thrust_models::Model, E::Ty: PartialEq, +// { +// Result::unwrap(res) +// } + +// #[thrust::extern_spec_fn] +// #[thrust_macros::requires(thrust_models::exists(|x| res == Err(x)))] +// #[thrust_macros::ensures(Err(result) == res)] +// fn _extern_spec_result_unwrap_err(res: Result) -> E +// where T: thrust_models::Model, T::Ty: PartialEq, +// E: thrust_models::Model, E::Ty: PartialEq, +// { +// Result::unwrap_err(res) +// } + +// #[thrust::extern_spec_fn] +// #[thrust_macros::requires(true)] +// #[thrust_macros::ensures( +// thrust_models::exists(|x| res == Ok(x) && result == Some(x)) +// || thrust_models::exists(|x| res == Err(x) && result == None) +// )] +// fn _extern_spec_result_ok(res: Result) -> Option +// where T: thrust_models::Model, T::Ty: PartialEq, +// E: thrust_models::Model, E::Ty: PartialEq, +// { +// Result::ok(res) +// } + +// #[thrust::extern_spec_fn] +// #[thrust_macros::requires(true)] +// #[thrust_macros::ensures( +// thrust_models::exists(|x| res == Ok(x) && result == None) +// || thrust_models::exists(|x| res == Err(x) && result == Some(x)) +// )] +// fn _extern_spec_result_err(res: Result) -> Option +// where T: thrust_models::Model, T::Ty: PartialEq, +// E: thrust_models::Model, E::Ty: PartialEq, +// { +// Result::err(res) +// } + +// #[thrust::extern_spec_fn] +// #[thrust_macros::requires(true)] +// #[thrust_macros::ensures( +// thrust_models::exists(|x| *res == Ok(x) && result == true) +// || thrust_models::exists(|x| *res == Err(x) && result == false) +// )] +// fn _extern_spec_result_is_ok(res: &Result) -> bool +// where T: thrust_models::Model, T::Ty: PartialEq, +// E: thrust_models::Model, E::Ty: PartialEq, +// { +// Result::is_ok(res) +// } + +// #[thrust::extern_spec_fn] +// #[thrust_macros::requires(true)] +// #[thrust_macros::ensures( +// thrust_models::exists(|x| *res == Ok(x) && result == false) +// || thrust_models::exists(|x| *res == Err(x) && result == true) +// )] +// fn _extern_spec_result_is_err(res: &Result) -> bool +// where T: thrust_models::Model, T::Ty: PartialEq, +// E: thrust_models::Model, E::Ty: PartialEq, +// { +// Result::is_err(res) +// } + +// #[thrust::extern_spec_fn] +// #[thrust_macros::requires(true)] // TODO: require x != i32::MIN +// #[thrust_macros::ensures(result >= 0 && (result == x || result == -x))] +// fn _extern_spec_i32_abs(x: i32) -> i32 { +// i32::abs(x) +// } + +// #[thrust::extern_spec_fn] +// #[thrust_macros::requires(true)] +// #[thrust_macros::ensures( +// (x >= y && result == (x - y)) +// || (x < y && result == (y - x)) +// )] +// fn _extern_spec_i32_abs_diff(x: i32, y: i32) -> u32 { +// i32::abs_diff(x, y) +// } + +// #[thrust::extern_spec_fn] +// #[thrust_macros::requires(true)] +// #[thrust_macros::ensures((x == 0 && result == 0) || (x > 0 && result == 1) || (x < 0 && result == -1))] +// fn _extern_spec_i32_signum(x: i32) -> i32 { +// i32::signum(x) +// } + +// #[thrust::extern_spec_fn] +// #[thrust_macros::requires(true)] +// #[thrust_macros::ensures((x < 0 && result == false) || (x >= 0 && result == true))] +// fn _extern_spec_i32_is_positive(x: i32) -> bool { +// i32::is_positive(x) +// } + +// #[thrust::extern_spec_fn] +// #[thrust_macros::requires(true)] +// #[thrust_macros::ensures((x <= 0 && result == true) || (x > 0 && result == false))] +// fn _extern_spec_i32_is_negative(x: i32) -> bool { +// i32::is_negative(x) +// } + +// #[thrust::extern_spec_fn] +// #[thrust_macros::requires(true)] +// #[thrust_macros::ensures(result.1 == 0)] +// fn _extern_spec_vec_new() -> Vec where T: thrust_models::Model, T::Ty: PartialEq { +// Vec::::new() +// } + +// #[thrust::extern_spec_fn] +// #[thrust_macros::requires(true)] +// #[thrust_macros::ensures(!vec == thrust_models::model::Vec((*vec).0.store((*vec).1, elem), (*vec).1 + 1))] +// fn _extern_spec_vec_push(vec: &mut Vec, elem: T) +// where T: thrust_models::Model, T::Ty: PartialEq +// { +// Vec::push(vec, elem) +// } + +// #[thrust::extern_spec_fn] +// #[thrust_macros::requires(true)] +// #[thrust_macros::ensures(result == vec.1)] +// fn _extern_spec_vec_len(vec: &Vec) -> usize where T: thrust_models::Model, T::Ty: PartialEq { +// Vec::len(vec) +// } + +// #[thrust::extern_spec_fn] +// #[thrust_macros::requires(index < vec.1)] +// #[thrust_macros::ensures(*result == vec.0[index])] +// fn _extern_spec_vec_index(vec: &Vec, index: usize) -> &T where T: thrust_models::Model, T::Ty: PartialEq { +// as std::ops::Index>::index(vec, index) +// } + +// #[thrust::extern_spec_fn] +// #[thrust_macros::requires(index < (*vec).1)] +// #[thrust_macros::ensures( +// *result == (*vec).0[index] && +// !result == (!vec).0[index] && +// !vec == thrust_models::model::Vec((*vec).0.store(index, !result), (*vec).1) +// )] +// fn _extern_spec_vec_index_mut(vec: &mut Vec, index: usize) -> &mut T +// where T: thrust_models::Model, T::Ty: PartialEq +// { +// as std::ops::IndexMut>::index_mut(vec, index) +// } + +// #[thrust::extern_spec_fn] +// #[thrust_macros::requires(true)] +// #[thrust_macros::ensures((!vec).1 == 0)] +// fn _extern_spec_vec_clear(vec: &mut Vec) where T: thrust_models::Model, T::Ty: PartialEq { +// Vec::clear(vec) +// } + +// #[thrust::extern_spec_fn] +// #[thrust_macros::requires(true)] +// #[thrust_macros::ensures( +// (!vec).0 == (*vec).0 && ( +// ( +// (*vec).1 > 0 && +// (!vec).1 == (*vec).1 - 1 && +// result == Some((*vec).0[(*vec).1 - 1]) +// ) || ( +// (*vec).1 == 0 && +// (!vec).1 == 0 && +// result == None +// ) +// ) +// )] +// fn _extern_spec_vec_pop(vec: &mut Vec) -> Option where T: thrust_models::Model, T::Ty: PartialEq { +// Vec::pop(vec) +// } + +// #[thrust::extern_spec_fn] +// #[thrust_macros::requires(true)] +// #[thrust_macros::ensures(result == ((*vec).1 == 0))] +// fn _extern_spec_vec_is_empty(vec: &Vec) -> bool where T: thrust_models::Model, T::Ty: PartialEq { +// Vec::is_empty(vec) +// } + +// #[thrust::extern_spec_fn] +// #[thrust_macros::requires(true)] +// #[thrust_macros::ensures( +// ( +// (*vec).1 > len && +// !vec == thrust_models::model::Vec((*vec).0, len) +// ) || ( +// (*vec).1 <= len && +// !vec == *vec +// ) +// )] +// fn _extern_spec_vec_truncate(vec: &mut Vec, len: usize) where T: thrust_models::Model, T::Ty: PartialEq { +// Vec::truncate(vec, len) +// } + +// // TODO: The following specs of some trait methods are too restrictive; we should allow for a +// // per-impl spec once we can describe the spec of blanket impls. + +// #[thrust::extern_spec_fn] +// #[thrust_macros::requires(true)] +// #[thrust_macros::ensures(result == (*x == *y))] +// fn _extern_spec_partialeq_eq(x: &T, y: &T) -> bool +// where T: thrust_models::Model + PartialEq, T::Ty: PartialEq +// { +// PartialEq::eq(x, y) +// } + +// #[thrust::extern_spec_fn] +// #[thrust_macros::requires(true)] +// #[thrust_macros::ensures(result == (*x < *y))] +// fn _extern_spec_partialord_lt(x: &T, y: &T) -> bool +// where T: thrust_models::Model + PartialOrd, T::Ty: PartialOrd +// { +// PartialOrd::lt(x, y) +// } + +// #[thrust::extern_spec_fn] +// #[thrust_macros::requires(true)] +// #[thrust_macros::ensures(result == (*x > *y))] +// fn _extern_spec_partialord_gt(x: &T, y: &T) -> bool +// where T: thrust_models::Model + PartialOrd, T::Ty: PartialOrd +// { +// PartialOrd::gt(x, y) +// } From 6d82bfbded4dc45d8d18466af7a35fd9c88f61e6 Mon Sep 17 00:00:00 2001 From: coeff-aij <175928954+coeff-aij@users.noreply.github.com> Date: Mon, 8 Jun 2026 12:09:21 +0900 Subject: [PATCH 012/142] add: imcomplete support for alias types --- src/analyze.rs | 13 ++++++++++--- src/refine/template.rs | 17 +++++++++++++---- 2 files changed, 23 insertions(+), 7 deletions(-) diff --git a/src/analyze.rs b/src/analyze.rs index 2545baff..2ba4a983 100644 --- a/src/analyze.rs +++ b/src/analyze.rs @@ -8,6 +8,7 @@ use std::cell::RefCell; use std::collections::HashMap; +use std::hash::Hash; use std::rc::Rc; use rustc_hir::lang_items::LangItem; @@ -19,7 +20,7 @@ use rustc_span::Symbol; use crate::analyze; use crate::annot::{AnnotFormula, AnnotParser, Resolver}; -use crate::chc; +use crate::chc::{self, ForallSortIdx}; use crate::pretty::PrettyDisplayExt as _; use crate::refine::{self, BasicBlockType, TypeBuilder}; use crate::rty; @@ -197,7 +198,13 @@ impl refine::EnumDefProvider for Rc> { } pub type Env = refine::Env>>; -pub type TypeParams = HashMap<(DefId, u32), chc::ForallSortIdx>; +pub type TypeParamMap = HashMap; + +#[derive(Eq, PartialEq, Hash)] +pub enum TypeParam { + GenericType(DefId, u32), + AssocType(DefId), +} #[derive(Debug, Clone)] struct DeferredFormulaFnDef<'tcx> { @@ -226,7 +233,7 @@ pub struct Analyzer<'tcx> { enum_defs: Rc>, - type_params: Rc>, + type_params: Rc>, } impl<'tcx> crate::refine::TemplateRegistry for Analyzer<'tcx> { diff --git a/src/refine/template.rs b/src/refine/template.rs index 37aa9a67..ccbf8761 100644 --- a/src/refine/template.rs +++ b/src/refine/template.rs @@ -8,7 +8,7 @@ use rustc_middle::ty as mir_ty; use rustc_span::def_id::DefId; use super::basic_block::BasicBlockType; -use crate::analyze::{DefIdCache, TypeParams}; +use crate::analyze::{DefIdCache, TypeParam, TypeParamMap}; use crate::chc; use crate::refine; use crate::rty; @@ -75,7 +75,7 @@ pub struct TypeBuilder<'tcx> { def_ids: DefIdCache<'tcx>, def_id: DefId, typing_env: mir_ty::TypingEnv<'tcx>, - type_params: Rc>, + type_params: Rc>, system: Rc>, } @@ -84,7 +84,7 @@ impl<'tcx> TypeBuilder<'tcx> { tcx: mir_ty::TyCtxt<'tcx>, def_ids: DefIdCache<'tcx>, def_id: DefId, - type_params: Rc>, + type_params: Rc>, system: Rc>, ) -> Self { let typing_env = mir_ty::TypingEnv::post_analysis(tcx, def_id); @@ -101,7 +101,15 @@ impl<'tcx> TypeBuilder<'tcx> { fn translate_param_type(&self, ty: &mir_ty::ParamTy) -> rty::Type { let mut type_params = self.type_params.borrow_mut(); let index = type_params - .entry((self.def_id, ty.index)) + .entry(TypeParam::GenericType(self.def_id, ty.index)) + .or_insert_with(|| self.system.borrow_mut().new_forall_sort()); + rty::ParamType::new(*index).into() + } + + fn translate_alias_type(&self, ty: &mir_ty::AliasTy) -> rty::Type { + let mut type_params = self.type_params.borrow_mut(); + let index = type_params + .entry(TypeParam::AssocType(ty.def_id)) .or_insert_with(|| self.system.borrow_mut().new_forall_sort()); rty::ParamType::new(*index).into() } @@ -254,6 +262,7 @@ impl<'tcx> TypeBuilder<'tcx> { unimplemented!("unsupported ADT: {:?}", ty); } } + mir_ty::TyKind::Alias(_, ty) => self.translate_alias_type(ty), kind => unimplemented!("unrefined_ty: {:?}", kind), } } From a0df8dc4cf8c6bd26162e8b33ddfbf9cb0ea44d4 Mon Sep 17 00:00:00 2001 From: coeff-aij <175928954+coeff-aij@users.noreply.github.com> Date: Wed, 27 May 2026 14:15:33 +0900 Subject: [PATCH 013/142] change: use Type::Param and chc::Sort::Forall for type prameters constrained with trait bounds --- src/analyze/crate_.rs | 20 +++++--------------- src/rty/subtyping.rs | 3 ++- 2 files changed, 7 insertions(+), 16 deletions(-) diff --git a/src/analyze/crate_.rs b/src/analyze/crate_.rs index fb7a01b3..5fe2769f 100644 --- a/src/analyze/crate_.rs +++ b/src/analyze/crate_.rs @@ -8,7 +8,7 @@ use rustc_span::def_id::LocalDefId; use crate::analyze; use crate::chc; -use crate::rty::{self, ClauseBuilderExt as _}; +use crate::rty::ClauseBuilderExt as _; /// An implementation of local crate analysis. /// @@ -139,16 +139,8 @@ impl<'tcx, 'ctx> Analyzer<'tcx, 'ctx> { // check polymorphic function def by replacing type params with some opaque type // (and this is no-op if the function is mono) - let mut expected = expected.clone(); - let subst = rty::TypeParamSubst::new( - expected - .free_ty_params() - .into_iter() - .map(|ty_param| (ty_param, rty::RefinedType::unrefined(rty::Type::int()))) - .collect(), - ); + let expected = expected.clone(); tracing::debug!("expected type of {:?} is {:#?}", local_def_id, expected); - expected.subst_ty_params(&subst); let generic_args = self.placeholder_generic_args(*local_def_id); self.ctx .local_def_analyzer(*local_def_id) @@ -187,12 +179,10 @@ impl<'tcx, 'ctx> Analyzer<'tcx, 'ctx> { let arg = match param.kind { mir_ty::GenericParamDefKind::Type { .. } => { if constrained_params.contains(¶m.index) { - panic!( - "unable to check generic function with constrained type parameter: {}", - self.tcx.def_path_str(local_def_id) - ); + mir_ty::Ty::new_param(self.tcx, param.index, param.name).into() + } else { + self.tcx.types.i32.into() } - self.tcx.types.i32.into() } mir_ty::GenericParamDefKind::Const { .. } => { unimplemented!() diff --git a/src/rty/subtyping.rs b/src/rty/subtyping.rs index 03477f02..1f015342 100644 --- a/src/rty/subtyping.rs +++ b/src/rty/subtyping.rs @@ -73,7 +73,8 @@ where (Type::Int, Type::Int) | (Type::Bool, Type::Bool) | (Type::String, Type::String) - | (Type::Never, Type::Never) => {} + | (Type::Never, Type::Never) + | (Type::Param(_), Type::Param(_)) => {} (Type::Enum(got), Type::Enum(expected)) if got.symbol() == expected.symbol() => { for (got_ty, expected_ty) in got.args.iter().zip(expected.args.iter()) { let cs = self.relate_sub_refined_type(got_ty, expected_ty); From 85d9e0192dbcfe6d97d09fb20012660bee42e554 Mon Sep 17 00:00:00 2001 From: coeff-aij <175928954+coeff-aij@users.noreply.github.com> Date: Wed, 27 May 2026 14:21:21 +0900 Subject: [PATCH 014/142] add: ForallPred represents unresolved user-defined predicates --- src/chc.rs | 40 ++++++++++++++++++++++++++++++++++++++++ src/chc/unbox.rs | 1 + 2 files changed, 41 insertions(+) diff --git a/src/chc.rs b/src/chc.rs index 3d32d7c4..88e354da 100644 --- a/src/chc.rs +++ b/src/chc.rs @@ -1044,6 +1044,32 @@ impl UserDefinedPred { } } +#[derive(Debug, Clone, PartialEq, Eq, Hash)] +pub struct ForallPred { + inner: String, +} + +impl std::fmt::Display for ForallPred { + fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result { + self.inner.fmt(f) + } +} + +impl<'a, D> Pretty<'a, D, termcolor::ColorSpec> for &ForallPred +where + D: pretty::DocAllocator<'a, termcolor::ColorSpec>, +{ + fn pretty(self, allocator: &'a D) -> pretty::DocBuilder<'a, D, termcolor::ColorSpec> { + allocator.text(self.inner.clone()) + } +} + +impl ForallPred { + pub fn new(inner: String) -> Self { + Self { inner } + } +} + /// A predicate. #[derive(Debug, Clone, PartialEq, Eq, Hash)] pub enum Pred { @@ -1051,6 +1077,7 @@ pub enum Pred { Var(PredVarId), Matcher(MatcherPred), UserDefined(UserDefinedPred), + ForallPred(ForallPred), } impl std::fmt::Display for Pred { @@ -1060,6 +1087,7 @@ impl std::fmt::Display for Pred { Pred::Var(p) => p.fmt(f), Pred::Matcher(p) => p.fmt(f), Pred::UserDefined(p) => p.fmt(f), + Pred::ForallPred(p) => p.fmt(f), } } } @@ -1075,6 +1103,7 @@ where Pred::Var(p) => p.pretty(allocator), Pred::Matcher(p) => p.pretty(allocator), Pred::UserDefined(p) => p.pretty(allocator), + Pred::ForallPred(p) => p.pretty(allocator), } } } @@ -1103,6 +1132,12 @@ impl From for Pred { } } +impl From for Pred { + fn from(p: ForallPred) -> Self { + Pred::ForallPred(p) + } +} + impl Pred { pub fn name(&self) -> std::borrow::Cow<'static, str> { match self { @@ -1110,6 +1145,7 @@ impl Pred { Pred::Var(p) => p.to_string().into(), Pred::Matcher(p) => p.name().into(), Pred::UserDefined(p) => p.to_string().into(), + Pred::ForallPred(p) => p.to_string().into(), } } @@ -1119,6 +1155,7 @@ impl Pred { Pred::Var(_) => false, Pred::Matcher(_) => false, Pred::UserDefined(_) => false, + Pred::ForallPred(_) => false, } } @@ -1128,6 +1165,7 @@ impl Pred { Pred::Var(_) => false, Pred::Matcher(_) => false, Pred::UserDefined(_) => false, + Pred::ForallPred(_) => false, } } @@ -1137,6 +1175,7 @@ impl Pred { Pred::Var(_) => false, Pred::Matcher(_) => false, Pred::UserDefined(_) => false, + Pred::ForallPred(_) => false, } } @@ -1146,6 +1185,7 @@ impl Pred { Pred::Var(_) => false, Pred::Matcher(_) => false, Pred::UserDefined(_) => false, + Pred::ForallPred(_) => false, } } } diff --git a/src/chc/unbox.rs b/src/chc/unbox.rs index 08d36c4e..edf3f854 100644 --- a/src/chc/unbox.rs +++ b/src/chc/unbox.rs @@ -43,6 +43,7 @@ fn unbox_pred(pred: Pred) -> Pred { Pred::Var(pred) => Pred::Var(pred), Pred::Matcher(pred) => unbox_matcher_pred(pred), Pred::UserDefined(pred) => Pred::UserDefined(pred), + Pred::ForallPred(pred) => Pred::ForallPred(pred), } } From 4d381fc7933bfcae1061ba38e08b219b202d3a2c Mon Sep 17 00:00:00 2001 From: coeff-aij <175928954+coeff-aij@users.noreply.github.com> Date: Wed, 27 May 2026 14:22:22 +0900 Subject: [PATCH 015/142] change: replace unresolved user-defined predicates with ForallPred --- src/analyze/annot_fn.rs | 58 +++++++++++++++++++++++++++++------------ src/refine.rs | 13 ++++++--- 2 files changed, 50 insertions(+), 21 deletions(-) diff --git a/src/analyze/annot_fn.rs b/src/analyze/annot_fn.rs index 2bbbe673..ca78a99e 100644 --- a/src/analyze/annot_fn.rs +++ b/src/analyze/annot_fn.rs @@ -3,7 +3,7 @@ use std::collections::HashMap; use pretty::{termcolor, Pretty}; use rustc_hir::{def_id::LocalDefId, HirId}; use rustc_index::IndexVec; -use rustc_middle::ty::{self as mir_ty, TyCtxt}; +use rustc_middle::ty::{self as mir_ty, TyCtxt, TypeFoldable}; use crate::analyze::{self, did_cache::DefIdCache}; use crate::annot::AnnotFormula; @@ -219,29 +219,48 @@ impl<'a, 'tcx> AnnotFnTranslator<'a, 'tcx> { } } + fn instantiate_generics( + &self, + ty: T, + generic_args: mir_ty::GenericArgsRef<'tcx>, + ) -> Option + where + T: TypeFoldable>, + { + if !self.generic_args.is_empty() { + Some(mir_ty::EarlyBinder::bind(ty).instantiate(self.tcx, generic_args)) + } else { + None + } + } + fn expr_ty(&self, expr: &'tcx rustc_hir::Expr<'tcx>) -> mir_ty::Ty<'tcx> { let ty = self.typeck.expr_ty(expr); - let instantiated = mir_ty::EarlyBinder::bind(ty).instantiate(self.tcx, self.generic_args); + let instantiated = self + .instantiate_generics(ty, self.generic_args) + .unwrap_or(ty); let typing_env = mir_ty::TypingEnv::fully_monomorphized(); self.tcx.normalize_erasing_regions(typing_env, instantiated) } fn pat_ty(&self, pat: &'tcx rustc_hir::Pat<'tcx>) -> mir_ty::Ty<'tcx> { let ty = self.typeck.pat_ty(pat); - let instantiated = mir_ty::EarlyBinder::bind(ty).instantiate(self.tcx, self.generic_args); + let instantiated = self + .instantiate_generics(ty, self.generic_args) + .unwrap_or(ty); let typing_env = mir_ty::TypingEnv::fully_monomorphized(); self.tcx.normalize_erasing_regions(typing_env, instantiated) } pub fn to_formula_fn(&self) -> FormulaFn<'tcx> { let formula = self.to_formula(self.body.value); - let params = self - .tcx - .fn_sig(self.local_def_id.to_def_id()) - .instantiate(self.tcx, self.generic_args) - .skip_binder() - .inputs() - .to_vec(); + let fn_sig = self.tcx.fn_sig(self.local_def_id.to_def_id()); + let binder = if self.generic_args.is_empty() { + fn_sig.skip_binder() + } else { + fn_sig.instantiate(self.tcx, self.generic_args) + }; + let params = binder.skip_binder().inputs().to_vec(); FormulaFn { params: IndexVec::from_raw(params), formula, @@ -630,8 +649,12 @@ impl<'a, 'tcx> AnnotFnTranslator<'a, 'tcx> { outer_generic_args = ?self.generic_args, "resolving predicate call in formula" ); - let generic_args = mir_ty::EarlyBinder::bind(generic_args) - .instantiate(self.tcx, self.generic_args); + let (is_unresolved_args, generic_args) = + match self.instantiate_generics(generic_args, self.generic_args) { + Some(args) => (false, args), + None => (true, generic_args), + }; + let instance = mir_ty::Instance::try_resolve( self.tcx, typing_env, @@ -639,14 +662,15 @@ impl<'a, 'tcx> AnnotFnTranslator<'a, 'tcx> { generic_args, ) .unwrap(); - let pred_def_id = if let Some(instance) = instance { - instance.def_id() + let pred_def_id = instance.map_or(def_id, |instance| instance.def_id()); + + let pred = if is_unresolved_args { + refine::user_defined_pred(self.tcx, pred_def_id).into() } else { - def_id + refine::forall_pred(self.tcx, pred_def_id).into() }; - let pred = refine::user_defined_pred(self.tcx, pred_def_id); let arg_terms = args.iter().map(|e| self.to_term(e)).collect(); - let atom = chc::Atom::new(pred.into(), arg_terms); + let atom = chc::Atom::new(pred, arg_terms); return FormulaOrTerm::Formula(chc::Formula::Atom(atom)); } } diff --git a/src/refine.rs b/src/refine.rs index 5a1fd8d3..cbbd37c4 100644 --- a/src/refine.rs +++ b/src/refine.rs @@ -18,15 +18,16 @@ pub use env::{ Assumption, EnumDefProvider, Env, PlaceType, PlaceTypeBuilder, PlaceTypeVar, TempVarIdx, Var, }; -use crate::chc::{DatatypeSymbol, UserDefinedPred}; +use crate::chc::{DatatypeSymbol, ForallPred, UserDefinedPred}; use rustc_middle::ty as mir_ty; use rustc_span::def_id::DefId; -fn stable_def_id_symbol(tcx: mir_ty::TyCtxt<'_>, did: DefId) -> String { +fn stable_def_id_symbol(tcx: mir_ty::TyCtxt<'_>, did: DefId, prefix: &str) -> String { let hash = tcx.def_path_hash(did); let path = tcx.def_path(did); if let Some(name) = path.data.last().and_then(|d| d.data.get_opt_name()) { - format!("{}_{}", name, hash.0.to_hex()) + tracing::debug!("stable_def_id_symbol: name={}", name); + format!("{}_{}_{}", prefix, name, hash.0.to_hex()) } else { hash.0.to_hex() } @@ -37,5 +38,9 @@ pub fn datatype_symbol(tcx: mir_ty::TyCtxt<'_>, did: DefId) -> DatatypeSymbol { } pub fn user_defined_pred(tcx: mir_ty::TyCtxt<'_>, did: DefId) -> UserDefinedPred { - UserDefinedPred::new(stable_def_id_symbol(tcx, did)) + UserDefinedPred::new(stable_def_id_symbol(tcx, did, "p")) +} + +pub fn forall_pred(tcx: mir_ty::TyCtxt<'_>, did: DefId) -> ForallPred { + ForallPred::new(stable_def_id_symbol(tcx, did, "q")) } From 14170e7bc8518786fd7196b41f3936d91b89ef25 Mon Sep 17 00:00:00 2001 From: coeff-aij <175928954+coeff-aij@users.noreply.github.com> Date: Wed, 27 May 2026 14:40:55 +0900 Subject: [PATCH 016/142] fix: distinguish different type parameters in subtyping --- src/rty/subtyping.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/rty/subtyping.rs b/src/rty/subtyping.rs index 1f015342..3b9cae09 100644 --- a/src/rty/subtyping.rs +++ b/src/rty/subtyping.rs @@ -73,8 +73,7 @@ where (Type::Int, Type::Int) | (Type::Bool, Type::Bool) | (Type::String, Type::String) - | (Type::Never, Type::Never) - | (Type::Param(_), Type::Param(_)) => {} + | (Type::Never, Type::Never) => {} (Type::Enum(got), Type::Enum(expected)) if got.symbol() == expected.symbol() => { for (got_ty, expected_ty) in got.args.iter().zip(expected.args.iter()) { let cs = self.relate_sub_refined_type(got_ty, expected_ty); @@ -124,6 +123,7 @@ where let cs2 = self.relate_sub_refined_type(&got.elem, &expected.elem); clauses.extend(cs2); } + (Type::Param(got), Type::Param(expected)) if got.idx == expected.idx => {} _ => panic!( "inconsistent types: got={}, expected={}", got.display(), From 6ee71de16dfb8944703bef3da41d264d4fa93ad4 Mon Sep 17 00:00:00 2001 From: coeff-aij <175928954+coeff-aij@users.noreply.github.com> Date: Wed, 27 May 2026 17:33:52 +0900 Subject: [PATCH 017/142] change: store the def_id of the corresponding function in TypeBuilder --- src/analyze.rs | 7 ++++--- src/analyze/basic_block.rs | 3 ++- src/analyze/crate_.rs | 9 ++++++++- src/analyze/local_def.rs | 2 +- src/refine/template.rs | 28 +++++++++++++++++++++------- 5 files changed, 36 insertions(+), 13 deletions(-) diff --git a/src/analyze.rs b/src/analyze.rs index 2ba4a983..1d9a77eb 100644 --- a/src/analyze.rs +++ b/src/analyze.rs @@ -654,15 +654,16 @@ impl<'tcx> Analyzer<'tcx> { &mut self, local_def_id: LocalDefId, bb: BasicBlock, + owner_fn_id: DefId ) -> basic_block::Analyzer<'tcx, '_> { - basic_block::Analyzer::new(self, local_def_id, bb) + basic_block::Analyzer::new(self, local_def_id, bb, owner_fn_id) } - pub fn type_builder(&self, def_ids: DefIdCache<'tcx>, def_id: DefId) -> TypeBuilder<'tcx> { + pub fn type_builder(&self, def_ids: DefIdCache<'tcx>, owner_fn_id: DefId) -> TypeBuilder<'tcx> { TypeBuilder::new( self.tcx, def_ids, - def_id, + owner_fn_id, self.type_params.clone(), self.system.clone(), ) diff --git a/src/analyze/basic_block.rs b/src/analyze/basic_block.rs index b7746f31..c00cec30 100644 --- a/src/analyze/basic_block.rs +++ b/src/analyze/basic_block.rs @@ -1327,6 +1327,7 @@ impl<'tcx, 'ctx> Analyzer<'tcx, 'ctx> { ctx: &'ctx mut analyze::Analyzer<'tcx>, local_def_id: LocalDefId, basic_block: BasicBlock, + owner_fn_id: DefId, ) -> Self { let tcx = ctx.tcx; let drop_points = DropPoints::default(); @@ -1334,7 +1335,7 @@ impl<'tcx, 'ctx> Analyzer<'tcx, 'ctx> { let env = ctx.new_env(); let local_decls = body.local_decls.clone(); let prophecy_vars = Default::default(); - let type_builder = ctx.type_builder(ctx.def_ids(), local_def_id.to_def_id()); + let type_builder = ctx.type_builder(ctx.def_ids(), owner_fn_id); Self { ctx, tcx, diff --git a/src/analyze/crate_.rs b/src/analyze/crate_.rs index 5fe2769f..c7c55b2c 100644 --- a/src/analyze/crate_.rs +++ b/src/analyze/crate_.rs @@ -179,7 +179,14 @@ impl<'tcx, 'ctx> Analyzer<'tcx, 'ctx> { let arg = match param.kind { mir_ty::GenericParamDefKind::Type { .. } => { if constrained_params.contains(¶m.index) { - mir_ty::Ty::new_param(self.tcx, param.index, param.name).into() + let new_param = + mir_ty::Ty::new_param(self.tcx, param.index, param.name).into(); + tracing::debug!( + "replace the cosnstrained param {:#?} with the new param {:#?}.", + param, + new_param + ); + new_param } else { self.tcx.types.i32.into() } diff --git a/src/analyze/local_def.rs b/src/analyze/local_def.rs index 3169ac03..ab10dde1 100644 --- a/src/analyze/local_def.rs +++ b/src/analyze/local_def.rs @@ -1057,7 +1057,7 @@ impl<'tcx, 'ctx> Analyzer<'tcx, 'ctx> { .clone(); let drop_points = self.drop_points[&bb].clone(); self.ctx - .basic_block_analyzer(self.local_def_id, bb) + .basic_block_analyzer(self.local_def_id, bb, self.body.source.def_id()) .body(self.body.clone()) .drop_points(drop_points) .run(&rty, expected_fn_ty); diff --git a/src/refine/template.rs b/src/refine/template.rs index ccbf8761..61e2acc4 100644 --- a/src/refine/template.rs +++ b/src/refine/template.rs @@ -73,7 +73,7 @@ where pub struct TypeBuilder<'tcx> { tcx: mir_ty::TyCtxt<'tcx>, def_ids: DefIdCache<'tcx>, - def_id: DefId, + owner_fn_id: DefId, typing_env: mir_ty::TypingEnv<'tcx>, type_params: Rc>, system: Rc>, @@ -83,15 +83,16 @@ impl<'tcx> TypeBuilder<'tcx> { pub fn new( tcx: mir_ty::TyCtxt<'tcx>, def_ids: DefIdCache<'tcx>, - def_id: DefId, + owner_fn_id: DefId, type_params: Rc>, system: Rc>, ) -> Self { - let typing_env = mir_ty::TypingEnv::post_analysis(tcx, def_id); + tracing::debug!("TypeBuilder is created for {owner_fn_id:?}."); + let typing_env = mir_ty::TypingEnv::post_analysis(tcx, owner_fn_id); Self { tcx, def_ids, - def_id, + owner_fn_id, typing_env, type_params, system, @@ -101,8 +102,17 @@ impl<'tcx> TypeBuilder<'tcx> { fn translate_param_type(&self, ty: &mir_ty::ParamTy) -> rty::Type { let mut type_params = self.type_params.borrow_mut(); let index = type_params - .entry(TypeParam::GenericType(self.def_id, ty.index)) - .or_insert_with(|| self.system.borrow_mut().new_forall_sort()); + .entry(TypeParam::GenericType(self.owner_fn_id, ty.index)) + .or_insert_with(|| { + let idx = self.system.borrow_mut().new_forall_sort(); + tracing::debug!( + "issue the new ForallSortIdx {} for ParamTy {:?} at {:?}.", + idx, + ty, + self.owner_fn_id + ); + idx + }); rty::ParamType::new(*index).into() } @@ -110,7 +120,11 @@ impl<'tcx> TypeBuilder<'tcx> { let mut type_params = self.type_params.borrow_mut(); let index = type_params .entry(TypeParam::AssocType(ty.def_id)) - .or_insert_with(|| self.system.borrow_mut().new_forall_sort()); + .or_insert_with(|| { + let idx = self.system.borrow_mut().new_forall_sort(); + tracing::debug!("issue the new ForallSortIdx {} for AliasTy {:#?}.", idx, ty); + idx + }); rty::ParamType::new(*index).into() } From 6c8add1237f8d968e20a5996dde13fa8a67574a8 Mon Sep 17 00:00:00 2001 From: coeff-aij <175928954+coeff-aij@users.noreply.github.com> Date: Thu, 28 May 2026 14:15:56 +0900 Subject: [PATCH 018/142] fix: identify type parameters using the DefId of the owner(e.g. `fn f` for `T` in the body) --- src/analyze.rs | 3 ++- src/analyze/basic_block.rs | 5 +++-- src/analyze/local_def.rs | 2 +- src/refine/template.rs | 2 +- 4 files changed, 7 insertions(+), 5 deletions(-) diff --git a/src/analyze.rs b/src/analyze.rs index 1d9a77eb..e25df5cd 100644 --- a/src/analyze.rs +++ b/src/analyze.rs @@ -472,8 +472,9 @@ impl<'tcx> Analyzer<'tcx> { &mut self, def_id: DefId, generic_args: mir_ty::GenericArgsRef<'tcx>, + caller_def_id: DefId, ) -> Option { - let type_builder = self.type_builder(self.def_ids(), def_id); + let type_builder = self.type_builder(self.def_ids(), caller_def_id); let deferred_ty = match self.defs.get(&def_id)? { DefTy::Concrete(rty) => { diff --git a/src/analyze/basic_block.rs b/src/analyze/basic_block.rs index c00cec30..65b5a528 100644 --- a/src/analyze/basic_block.rs +++ b/src/analyze/basic_block.rs @@ -868,7 +868,8 @@ impl<'tcx, 'ctx> Analyzer<'tcx, 'ctx> { def_id: DefId, args: mir_ty::GenericArgsRef<'tcx>, ) -> rty::Type { - if let Some(def_ty) = self.ctx.def_ty_with_args(def_id, args) { + let caller_def_id = self.type_builder.owner_fn_id; + if let Some(def_ty) = self.ctx.def_ty_with_args(def_id, args, caller_def_id) { return def_ty.ty; } @@ -880,7 +881,7 @@ impl<'tcx, 'ctx> Analyzer<'tcx, 'ctx> { ); } tracing::info!(?def_id, ?resolved_def_id, ?resolved_args, "resolved"); - let Some(def_ty) = self.ctx.def_ty_with_args(resolved_def_id, resolved_args) else { + let Some(def_ty) = self.ctx.def_ty_with_args(resolved_def_id, resolved_args, caller_def_id) else { panic!( "unknown def (resolved): {:?}, args: {:?}", resolved_def_id, resolved_args diff --git a/src/analyze/local_def.rs b/src/analyze/local_def.rs index ab10dde1..0fcfb5a9 100644 --- a/src/analyze/local_def.rs +++ b/src/analyze/local_def.rs @@ -317,7 +317,7 @@ impl<'tcx, 'ctx> Analyzer<'tcx, 'ctx> { .associated_item(self.local_def_id.to_def_id()) .trait_item_def_id .unwrap(); - self.ctx.def_ty_with_args(trait_item_did, trait_ref.args) + self.ctx.def_ty_with_args(trait_item_did, trait_ref.args, trait_ref.def_id) } // TODO: Remove this eager precompute together with diff --git a/src/refine/template.rs b/src/refine/template.rs index 61e2acc4..34f2a38b 100644 --- a/src/refine/template.rs +++ b/src/refine/template.rs @@ -73,7 +73,7 @@ where pub struct TypeBuilder<'tcx> { tcx: mir_ty::TyCtxt<'tcx>, def_ids: DefIdCache<'tcx>, - owner_fn_id: DefId, + pub owner_fn_id: DefId, typing_env: mir_ty::TypingEnv<'tcx>, type_params: Rc>, system: Rc>, From af226a4367ae1dbb6f6dcd4caa079f0c3d4ac322 Mon Sep 17 00:00:00 2001 From: coeff-aij <175928954+coeff-aij@users.noreply.github.com> Date: Thu, 28 May 2026 20:51:51 +0900 Subject: [PATCH 019/142] fix: wrong conditionals to insert forall predicates --- src/analyze/annot_fn.rs | 14 ++++++++++---- 1 file changed, 10 insertions(+), 4 deletions(-) diff --git a/src/analyze/annot_fn.rs b/src/analyze/annot_fn.rs index ca78a99e..0cba0bca 100644 --- a/src/analyze/annot_fn.rs +++ b/src/analyze/annot_fn.rs @@ -649,7 +649,7 @@ impl<'a, 'tcx> AnnotFnTranslator<'a, 'tcx> { outer_generic_args = ?self.generic_args, "resolving predicate call in formula" ); - let (is_unresolved_args, generic_args) = + let (mut is_unresolved_args, generic_args) = match self.instantiate_generics(generic_args, self.generic_args) { Some(args) => (false, args), None => (true, generic_args), @@ -662,13 +662,19 @@ impl<'a, 'tcx> AnnotFnTranslator<'a, 'tcx> { generic_args, ) .unwrap(); - let pred_def_id = instance.map_or(def_id, |instance| instance.def_id()); + let pred_def_id = if let Some(instance) = instance { + instance.def_id() + } else { + is_unresolved_args = true; + def_id + }; let pred = if is_unresolved_args { - refine::user_defined_pred(self.tcx, pred_def_id).into() - } else { refine::forall_pred(self.tcx, pred_def_id).into() + } else { + refine::user_defined_pred(self.tcx, pred_def_id).into() }; + tracing::debug!("resolved predicate call in formula: {:?}", pred); let arg_terms = args.iter().map(|e| self.to_term(e)).collect(); let atom = chc::Atom::new(pred, arg_terms); return FormulaOrTerm::Formula(chc::Formula::Atom(atom)); From 3888011a0b53e55b658ef6638ad0a56e96d69772 Mon Sep 17 00:00:00 2001 From: coeff-aij <175928954+coeff-aij@users.noreply.github.com> Date: Thu, 28 May 2026 23:57:33 +0900 Subject: [PATCH 020/142] fix: normalize thrust::Model::Ty --- src/refine/template.rs | 15 ++++++++++++++- 1 file changed, 14 insertions(+), 1 deletion(-) diff --git a/src/refine/template.rs b/src/refine/template.rs index 34f2a38b..c20f4ba8 100644 --- a/src/refine/template.rs +++ b/src/refine/template.rs @@ -170,19 +170,32 @@ impl<'tcx> TypeBuilder<'tcx> { } fn resolve_model_ty(&self, orig_ty: mir_ty::Ty<'tcx>) -> mir_ty::Ty<'tcx> { + tracing::debug!("attempting to resolve the type {:#?}.", orig_ty); let ty = self.replace_closure_model(orig_ty); let Some(model_ty_def_id) = self.def_ids.model_ty() else { return ty; }; let args = self.tcx.mk_args(&[ty.into()]); + tracing::debug!("generic args are {:#?}.", args); let projection_ty = mir_ty::Ty::new_projection(self.tcx, model_ty_def_id, args); if let Ok(normalized_ty) = self .tcx .try_normalize_erasing_regions(self.typing_env, projection_ty) { - return normalized_ty; + tracing::debug!("the type {:#?} is resolved as the type {:#?}.", orig_ty, ty); + let contains_model_ty_alias = normalized_ty.walk().any(|arg| { + if let mir_ty::GenericArgKind::Type(t) = arg.kind() { + matches!(t.kind(), mir_ty::TyKind::Alias(_, alias_ty) if alias_ty.def_id == model_ty_def_id) + } else { + false + } + }); + if !contains_model_ty_alias { + return normalized_ty; + } } + tracing::debug!("the type {:#?} is replaced as the {:#?}.", orig_ty, ty); ty } From 968a4a7576c99352336b3975eb9127c9aa60183c Mon Sep 17 00:00:00 2001 From: coeff-aij <175928954+coeff-aij@users.noreply.github.com> Date: Mon, 8 Jun 2026 12:17:20 +0900 Subject: [PATCH 021/142] fix: propagate the DefId for the owner function of formula_fn and extern_spec_fn --- src/analyze.rs | 15 +++- src/analyze/annot_fn.rs | 9 +- src/analyze/basic_block.rs | 5 +- src/analyze/crate_.rs | 10 +-- src/analyze/local_def.rs | 163 ++++++++++++++++++++++--------------- 5 files changed, 119 insertions(+), 83 deletions(-) diff --git a/src/analyze.rs b/src/analyze.rs index e25df5cd..439b1456 100644 --- a/src/analyze.rs +++ b/src/analyze.rs @@ -448,6 +448,7 @@ impl<'tcx> Analyzer<'tcx> { &self, local_def_id: LocalDefId, generic_args: mir_ty::GenericArgsRef<'tcx>, + owner_fn_id: DefId, ) -> Option> { let deferred_formula_fn = self.formula_fns.get(&local_def_id)?; @@ -458,7 +459,7 @@ impl<'tcx> Analyzer<'tcx> { let translator = annot_fn::AnnotFnTranslator::new(self, local_def_id) .with_generic_args(generic_args) - .with_def_id_cache(self.def_ids()); + .with_def_id_cache(self.def_ids(), owner_fn_id); let formula_fn = translator.to_formula_fn(); deferred_formula_fn_cache .borrow_mut() @@ -655,7 +656,7 @@ impl<'tcx> Analyzer<'tcx> { &mut self, local_def_id: LocalDefId, bb: BasicBlock, - owner_fn_id: DefId + owner_fn_id: DefId, ) -> basic_block::Analyzer<'tcx, '_> { basic_block::Analyzer::new(self, local_def_id, bb, owner_fn_id) } @@ -761,6 +762,7 @@ impl<'tcx> Analyzer<'tcx> { resolver: T, self_type_name: Option, generic_args: mir_ty::GenericArgsRef<'tcx>, + owner_fn_id: DefId, ) -> Option> where T: Resolver, @@ -791,7 +793,9 @@ impl<'tcx> Analyzer<'tcx> { if require_annot.is_some() { unimplemented!(); } - let Some(formula_fn) = self.formula_fn_with_args(formula_def_id, generic_args) else { + let Some(formula_fn) = + self.formula_fn_with_args(formula_def_id, generic_args, owner_fn_id) + else { panic!( "require annotation {:?} is not a formula function", formula_def_id @@ -810,6 +814,7 @@ impl<'tcx> Analyzer<'tcx> { resolver: T, self_type_name: Option, generic_args: mir_ty::GenericArgsRef<'tcx>, + owner_fn_id: DefId, ) -> Option> where T: Resolver>, @@ -841,7 +846,9 @@ impl<'tcx> Analyzer<'tcx> { if ensure_annot.is_some() { unimplemented!(); } - let Some(formula_fn) = self.formula_fn_with_args(formula_def_id, generic_args) else { + let Some(formula_fn) = + self.formula_fn_with_args(formula_def_id, generic_args, owner_fn_id) + else { panic!( "ensure annotation {:?} is not a formula function", formula_def_id diff --git a/src/analyze/annot_fn.rs b/src/analyze/annot_fn.rs index 0cba0bca..a9c6f234 100644 --- a/src/analyze/annot_fn.rs +++ b/src/analyze/annot_fn.rs @@ -1,7 +1,10 @@ use std::collections::HashMap; use pretty::{termcolor, Pretty}; -use rustc_hir::{def_id::LocalDefId, HirId}; +use rustc_hir::{ + def_id::{DefId, LocalDefId}, + HirId, +}; use rustc_index::IndexVec; use rustc_middle::ty::{self as mir_ty, TyCtxt, TypeFoldable}; @@ -178,12 +181,12 @@ impl<'a, 'tcx> AnnotFnTranslator<'a, 'tcx> { self } - pub fn with_def_id_cache(mut self, def_ids: DefIdCache<'tcx>) -> Self { + pub fn with_def_id_cache(mut self, def_ids: DefIdCache<'tcx>, owner_fn_id: DefId) -> Self { self.def_ids = def_ids; self.type_builder = TypeBuilder::new( self.tcx, self.def_ids.clone(), - self.local_def_id.to_def_id(), + owner_fn_id, self.analyzer.type_params.clone(), self.analyzer.system.clone(), ); diff --git a/src/analyze/basic_block.rs b/src/analyze/basic_block.rs index 65b5a528..d7447024 100644 --- a/src/analyze/basic_block.rs +++ b/src/analyze/basic_block.rs @@ -881,7 +881,10 @@ impl<'tcx, 'ctx> Analyzer<'tcx, 'ctx> { ); } tracing::info!(?def_id, ?resolved_def_id, ?resolved_args, "resolved"); - let Some(def_ty) = self.ctx.def_ty_with_args(resolved_def_id, resolved_args, caller_def_id) else { + let Some(def_ty) = self + .ctx + .def_ty_with_args(resolved_def_id, resolved_args, caller_def_id) + else { panic!( "unknown def (resolved): {:?}, args: {:?}", resolved_def_id, resolved_args diff --git a/src/analyze/crate_.rs b/src/analyze/crate_.rs index c7c55b2c..58bd309e 100644 --- a/src/analyze/crate_.rs +++ b/src/analyze/crate_.rs @@ -111,14 +111,9 @@ impl<'tcx, 'ctx> Analyzer<'tcx, 'ctx> { } } - let target_def_id = if analyzer.is_annotated_as_extern_spec_fn() { - analyzer.extern_spec_fn_target_def_id() - } else { - local_def_id.to_def_id() - }; - + let owner_fn_id = analyzer.owner_fn_id; let expected = analyzer.expected_ty(); - self.ctx.register_def(target_def_id, expected); + self.ctx.register_def(owner_fn_id, expected); } fn analyze_local_defs(&mut self) { @@ -140,7 +135,6 @@ impl<'tcx, 'ctx> Analyzer<'tcx, 'ctx> { // check polymorphic function def by replacing type params with some opaque type // (and this is no-op if the function is mono) let expected = expected.clone(); - tracing::debug!("expected type of {:?} is {:#?}", local_def_id, expected); let generic_args = self.placeholder_generic_args(*local_def_id); self.ctx .local_def_analyzer(*local_def_id) diff --git a/src/analyze/local_def.rs b/src/analyze/local_def.rs index 0fcfb5a9..6f717ef8 100644 --- a/src/analyze/local_def.rs +++ b/src/analyze/local_def.rs @@ -36,6 +36,82 @@ fn stmt_str_literal(stmt: &rustc_hir::Stmt) -> Option { } } +fn is_annotated_as_extern_spec_fn_impl(tcx: &TyCtxt, local_def_id: &LocalDefId) -> bool { + tcx.get_attrs_by_path( + local_def_id.to_def_id(), + &analyze::annot::extern_spec_fn_path(), + ) + .next() + .is_some() +} + +/// Extract the target DefId from `#[thrust::extern_spec_fn]` function. +/// +/// The target is identified as the tail call expression (last expression without +/// semicolon) in the function body block. +fn extern_spec_fn_target_def_id_impl<'tcx>( + tcx: &TyCtxt<'tcx>, + local_def_id: &LocalDefId, + mir_body: &Body<'tcx>, +) -> DefId { + let hir_node = tcx.hir_node_by_def_id(*local_def_id); + let hir_body_id = match hir_node { + rustc_hir::Node::Item(item) => { + let rustc_hir::ItemKind::Fn { body: body_id, .. } = item.kind else { + panic!("extern_spec_fn must be a function"); + }; + body_id + } + rustc_hir::Node::ImplItem(impl_item) => { + let rustc_hir::ImplItemKind::Fn(_, body_id) = impl_item.kind else { + panic!("extern_spec_fn must be a function"); + }; + body_id + } + rustc_hir::Node::TraitItem(trait_item) => { + let rustc_hir::TraitItemKind::Fn(_, rustc_hir::TraitFn::Provided(body_id)) = + trait_item.kind + else { + panic!("extern_spec_fn must be a function with a body"); + }; + body_id + } + _ => panic!("extern_spec_fn must be a function item or impl item"), + }; + + let hir_body = tcx.hir_body(hir_body_id); + + // The body is a block; the tail expression is the function call to the target. + let rustc_hir::ExprKind::Block(block, _) = &hir_body.value.kind else { + panic!("extern_spec_fn body must be a block"); + }; + let tail_expr = block + .expr + .expect("extern_spec_fn block must end with a tail call expression"); + + let rustc_hir::ExprKind::Call(func_expr, _) = &tail_expr.kind else { + panic!("extern_spec_fn tail expression must be a function call"); + }; + let rustc_hir::ExprKind::Path(qpath) = &func_expr.kind else { + panic!("extern_spec_fn call must be a path expression"); + }; + + let typeck_result = tcx.typeck(local_def_id); + let hir_id = func_expr.hir_id; + let rustc_hir::def::Res::Def(_, def_id) = typeck_result.qpath_res(qpath, hir_id) else { + panic!("extern_spec_fn call must resolve to a definition"); + }; + + let args = typeck_result.node_args(hir_id); + let typing_env = mir_body.typing_env(*tcx); + let instance = mir_ty::Instance::try_resolve(*tcx, typing_env, def_id, args).unwrap(); + if let Some(instance) = instance { + instance.def_id() + } else { + def_id + } +} + /// An implementation of the typing of local definitions. /// /// The current implementation only applies to function definitions. The entry point is @@ -45,6 +121,7 @@ pub struct Analyzer<'tcx, 'ctx> { tcx: TyCtxt<'tcx>, local_def_id: LocalDefId, + pub owner_fn_id: DefId, body: Body<'tcx>, /// to substitute HIR types during translation in [`crate::analyze::annot_fn`] @@ -200,13 +277,7 @@ impl<'tcx, 'ctx> Analyzer<'tcx, 'ctx> { } pub fn is_annotated_as_extern_spec_fn(&self) -> bool { - self.tcx - .get_attrs_by_path( - self.local_def_id.to_def_id(), - &analyze::annot::extern_spec_fn_path(), - ) - .next() - .is_some() + is_annotated_as_extern_spec_fn_impl(&self.tcx, &self.local_def_id) } pub fn is_annotated_as_predicate(&self) -> bool { @@ -317,7 +388,8 @@ impl<'tcx, 'ctx> Analyzer<'tcx, 'ctx> { .associated_item(self.local_def_id.to_def_id()) .trait_item_def_id .unwrap(); - self.ctx.def_ty_with_args(trait_item_did, trait_ref.args, trait_ref.def_id) + self.ctx + .def_ty_with_args(trait_item_did, trait_ref.args, trait_ref.def_id) } // TODO: Remove this eager precompute together with @@ -340,7 +412,9 @@ impl<'tcx, 'ctx> Analyzer<'tcx, 'ctx> { if fn_def_id == self.local_def_id.to_def_id() { continue; } - let _ = self.ctx.def_ty_with_args(fn_def_id, fn_args); + let _ = self + .ctx + .def_ty_with_args(fn_def_id, fn_args, self.owner_fn_id); } } @@ -386,6 +460,7 @@ impl<'tcx, 'ctx> Analyzer<'tcx, 'ctx> { ¶m_resolver, self_type_name.clone(), self.generic_args, + self.owner_fn_id, ); let mut ensure_annot = self.ctx.extract_ensure_annot( @@ -393,6 +468,7 @@ impl<'tcx, 'ctx> Analyzer<'tcx, 'ctx> { &result_param_resolver, self_type_name.clone(), self.generic_args, + self.owner_fn_id, ); if let Some(trait_item_id) = self.local_trait_item_id() { @@ -402,12 +478,14 @@ impl<'tcx, 'ctx> Analyzer<'tcx, 'ctx> { ¶m_resolver, self_type_name.clone(), self.generic_args, + self.owner_fn_id, ); let trait_ensure_annot = self.ctx.extract_ensure_annot( trait_item_id, &result_param_resolver, self_type_name.clone(), self.generic_args, + self.owner_fn_id, ); assert!(require_annot.is_none() || trait_require_annot.is_none()); @@ -475,62 +553,7 @@ impl<'tcx, 'ctx> Analyzer<'tcx, 'ctx> { /// The target is identified as the tail call expression (last expression without /// semicolon) in the function body block. pub fn extern_spec_fn_target_def_id(&self) -> DefId { - let node = self.tcx.hir_node_by_def_id(self.local_def_id); - let body_id = match node { - rustc_hir::Node::Item(item) => { - let rustc_hir::ItemKind::Fn { body: body_id, .. } = item.kind else { - panic!("extern_spec_fn must be a function"); - }; - body_id - } - rustc_hir::Node::ImplItem(impl_item) => { - let rustc_hir::ImplItemKind::Fn(_, body_id) = impl_item.kind else { - panic!("extern_spec_fn must be a function"); - }; - body_id - } - rustc_hir::Node::TraitItem(trait_item) => { - let rustc_hir::TraitItemKind::Fn(_, rustc_hir::TraitFn::Provided(body_id)) = - trait_item.kind - else { - panic!("extern_spec_fn must be a function with a body"); - }; - body_id - } - _ => panic!("extern_spec_fn must be a function item or impl item"), - }; - - let body = self.tcx.hir_body(body_id); - - // The body is a block; the tail expression is the function call to the target. - let rustc_hir::ExprKind::Block(block, _) = &body.value.kind else { - panic!("extern_spec_fn body must be a block"); - }; - let tail_expr = block - .expr - .expect("extern_spec_fn block must end with a tail call expression"); - - let rustc_hir::ExprKind::Call(func_expr, _) = &tail_expr.kind else { - panic!("extern_spec_fn tail expression must be a function call"); - }; - let rustc_hir::ExprKind::Path(qpath) = &func_expr.kind else { - panic!("extern_spec_fn call must be a path expression"); - }; - - let typeck_result = self.tcx.typeck(self.local_def_id); - let hir_id = func_expr.hir_id; - let rustc_hir::def::Res::Def(_, def_id) = typeck_result.qpath_res(qpath, hir_id) else { - panic!("extern_spec_fn call must resolve to a definition"); - }; - - let args = typeck_result.node_args(hir_id); - let typing_env = self.body.typing_env(self.tcx); - let instance = mir_ty::Instance::try_resolve(self.tcx, typing_env, def_id, args).unwrap(); - if let Some(instance) = instance { - instance.def_id() - } else { - def_id - } + extern_spec_fn_target_def_id_impl(&self.tcx, &self.local_def_id, &self.body) } fn is_mut_param(&self, param_idx: rty::FunctionParamIdx) -> bool { @@ -938,7 +961,7 @@ impl<'tcx, 'ctx> Analyzer<'tcx, 'ctx> { ) -> rty::Refinement { let formula_fn = self .ctx - .formula_fn_with_args(formula_def_id, generic_args) + .formula_fn_with_args(formula_def_id, generic_args, self.owner_fn_id) .expect("invariant formula function is not registered"); let idents = self.tcx.fn_arg_idents(formula_def_id.to_def_id()); @@ -1185,12 +1208,18 @@ impl<'tcx, 'ctx> Analyzer<'tcx, 'ctx> { let tcx = ctx.tcx; let body = tcx.optimized_mir(local_def_id.to_def_id()).clone(); let drop_points = Default::default(); - let type_builder = ctx.type_builder(ctx.def_ids(), local_def_id.to_def_id()); + let owner_fn_id = if is_annotated_as_extern_spec_fn_impl(&tcx, &local_def_id) { + extern_spec_fn_target_def_id_impl(&tcx, &local_def_id, &body) + } else { + local_def_id.to_def_id() + }; + let type_builder = ctx.type_builder(ctx.def_ids(), owner_fn_id); let generic_args = tcx.mk_args(&[]); Self { ctx, tcx, local_def_id, + owner_fn_id, body, generic_args, drop_points, From 87969f20e3969742b1a3d003ccf830ba63b31f2b Mon Sep 17 00:00:00 2001 From: coeff-aij <175928954+coeff-aij@users.noreply.github.com> Date: Fri, 29 May 2026 17:06:43 +0900 Subject: [PATCH 022/142] fix: use both TypeParamIdx and ForallSortIdx for type parameters --- src/refine/template.rs | 42 +++++++++++++------- src/rty.rs | 36 ++++++++++------- src/rty/params.rs | 89 +++++++++++++++++++++--------------------- src/rty/subtyping.rs | 3 +- 4 files changed, 95 insertions(+), 75 deletions(-) diff --git a/src/refine/template.rs b/src/refine/template.rs index c20f4ba8..e283b06d 100644 --- a/src/refine/template.rs +++ b/src/refine/template.rs @@ -75,6 +75,11 @@ pub struct TypeBuilder<'tcx> { def_ids: DefIdCache<'tcx>, pub owner_fn_id: DefId, typing_env: mir_ty::TypingEnv<'tcx>, + /// Maps index in [`mir_ty::ParamTy`] to [`rty::TypeParamIdx`]. + /// These indices may differ because we skip lifetime parameters and they always need to be + /// mapped when we translate a [`mir_ty::ParamTy`] to [`rty::ParamType`]. + /// See [`rty::TypeParamIdx`] for more details. + param_idx_mapping: HashMap, type_params: Rc>, system: Rc>, } @@ -87,6 +92,19 @@ impl<'tcx> TypeBuilder<'tcx> { type_params: Rc>, system: Rc>, ) -> Self { + let generics = tcx.generics_of(owner_fn_id); + let mut param_idx_mapping: HashMap = Default::default(); + for i in 0..generics.count() { + let generic_param = generics.param_at(i, tcx); + match generic_param.kind { + mir_ty::GenericParamDefKind::Lifetime => {} + mir_ty::GenericParamDefKind::Type { .. } => { + param_idx_mapping.insert(i as u32, param_idx_mapping.len().into()); + } + mir_ty::GenericParamDefKind::Const { .. } => {} + } + } + tracing::debug!("TypeBuilder is created for {owner_fn_id:?}."); let typing_env = mir_ty::TypingEnv::post_analysis(tcx, owner_fn_id); Self { @@ -94,14 +112,20 @@ impl<'tcx> TypeBuilder<'tcx> { def_ids, owner_fn_id, typing_env, + param_idx_mapping, type_params, system, } } fn translate_param_type(&self, ty: &mir_ty::ParamTy) -> rty::Type { + let param_local_idx = *self + .param_idx_mapping + .get(&ty.index) + .expect("unknown type param idx"); + let mut type_params = self.type_params.borrow_mut(); - let index = type_params + let forall_sort_idx = type_params .entry(TypeParam::GenericType(self.owner_fn_id, ty.index)) .or_insert_with(|| { let idx = self.system.borrow_mut().new_forall_sort(); @@ -113,19 +137,7 @@ impl<'tcx> TypeBuilder<'tcx> { ); idx }); - rty::ParamType::new(*index).into() - } - - fn translate_alias_type(&self, ty: &mir_ty::AliasTy) -> rty::Type { - let mut type_params = self.type_params.borrow_mut(); - let index = type_params - .entry(TypeParam::AssocType(ty.def_id)) - .or_insert_with(|| { - let idx = self.system.borrow_mut().new_forall_sort(); - tracing::debug!("issue the new ForallSortIdx {} for AliasTy {:#?}.", idx, ty); - idx - }); - rty::ParamType::new(*index).into() + rty::ParamType::new(param_local_idx, *forall_sort_idx).into() } /// Replaces {closure} types with thrust_models::Closure<{closure}>. @@ -289,7 +301,7 @@ impl<'tcx> TypeBuilder<'tcx> { unimplemented!("unsupported ADT: {:?}", ty); } } - mir_ty::TyKind::Alias(_, ty) => self.translate_alias_type(ty), + // mir_ty::TyKind::Alias(_, ty) => self.translate_alias_type(ty), kind => unimplemented!("unrefined_ty: {:?}", kind), } } diff --git a/src/rty.rs b/src/rty.rs index 8f6f930e..dcf9576f 100644 --- a/src/rty.rs +++ b/src/rty.rs @@ -43,7 +43,7 @@ use pretty::{termcolor, Pretty}; use rustc_abi::VariantIdx; use rustc_index::IndexVec; -use crate::chc; +use crate::chc::{self, ForallSortIdx}; mod template; pub use template::{Template, TemplateBuilder}; @@ -729,7 +729,8 @@ impl EnumType { /// A type parameter. #[derive(Debug, Clone)] pub struct ParamType { - idx: TypeParamIdx, + type_param_idx: TypeParamIdx, + forall_sort_idx: ForallSortIdx, } impl<'a, D> Pretty<'a, D, termcolor::ColorSpec> for &ParamType @@ -737,17 +738,24 @@ where D: pretty::DocAllocator<'a, termcolor::ColorSpec>, { fn pretty(self, allocator: &'a D) -> pretty::DocBuilder<'a, D, termcolor::ColorSpec> { - self.idx.pretty(allocator) + self.type_param_idx.pretty(allocator) } } impl ParamType { - pub fn new(idx: TypeParamIdx) -> Self { - ParamType { idx } + pub fn new(type_param_idx: TypeParamIdx, forall_sort_idx: ForallSortIdx) -> Self { + ParamType { + type_param_idx, + forall_sort_idx, + } + } + + pub fn type_param_index(&self) -> TypeParamIdx { + self.type_param_idx } - pub fn index(&self) -> TypeParamIdx { - self.idx + pub fn forall_sort_index(&self) -> ForallSortIdx { + self.forall_sort_idx } pub fn into_closed_ty(self) -> Type { @@ -1034,7 +1042,7 @@ impl Type { // currently String sort seems not available in HORN logic of Z3 Type::String => chc::Sort::null(), Type::Never => chc::Sort::null(), - Type::Param(ty) => chc::Sort::forall(ty.index()), + Type::Param(ty) => chc::Sort::forall(ty.forall_sort_index()), Type::Pointer(ty) => { let elem_sort = ty.elem.ty.to_sort(); @@ -1120,7 +1128,7 @@ impl Type { pub fn free_ty_params(&self) -> HashSet { match self { Type::Int | Type::Bool | Type::String | Type::Never => Default::default(), - Type::Param(ty) => std::iter::once(ty.index()).collect(), + Type::Param(ty) => std::iter::once(ty.type_param_index()).collect(), Type::Pointer(ty) => ty.free_ty_params(), Type::Function(ty) => ty.free_ty_params(), Type::Tuple(ty) => ty.free_ty_params(), @@ -1687,7 +1695,7 @@ impl RefinedType { match &mut self.ty { Type::Int | Type::Bool | Type::String | Type::Never => {} Type::Param(ty) => { - if let Some(rty) = subst.get(ty.index()) { + if let Some(rty) = subst.get(ty.type_param_index()) { let RefinedType { ty: replacement_ty, refinement, @@ -1723,15 +1731,15 @@ impl RefinedType { | (Type::Bool, Type::Bool) | (Type::String, Type::String) | (Type::Never, Type::Never) => Default::default(), - (Type::Param(pty), ty) if !ty.free_ty_params().contains(&pty.index()) => { + (Type::Param(pty), ty) if !ty.free_ty_params().contains(&pty.type_param_index()) => { TypeParamSubst::singleton( - pty.index(), + pty.type_param_index(), RefinedType::new(ty.clone(), other.refinement.clone()), ) } - (ty, Type::Param(pty)) if !ty.free_ty_params().contains(&pty.index()) => { + (ty, Type::Param(pty)) if !ty.free_ty_params().contains(&pty.type_param_index()) => { TypeParamSubst::singleton( - pty.index(), + pty.type_param_index(), RefinedType::new(ty.clone(), self.refinement.clone()), ) } diff --git a/src/rty/params.rs b/src/rty/params.rs index 88e0bf4e..ef05138e 100644 --- a/src/rty/params.rs +++ b/src/rty/params.rs @@ -2,57 +2,56 @@ use std::collections::BTreeMap; -// use pretty::{termcolor, Pretty}; +use pretty::{termcolor, Pretty}; use rustc_index::IndexVec; use crate::chc; use super::{Closed, RefinedType, Type}; -pub type TypeParamIdx = chc::ForallSortIdx; -// rustc_index::newtype_index! { -// /// An index representing a type parameter. -// /// -// /// ## Note on indexing of type parameters -// /// -// /// The index of [`rustc_middle::ty::ParamTy`] is based on all generic parameters in -// /// the definition, including lifetimes. Given the following definition: -// /// -// /// ```rust -// /// struct X<'a, T> { f: &'a T } -// /// ``` -// /// -// /// The type of field `f` is `&T1` (not `&T0`) in MIR. However, in Thrust, we ignore lifetime -// /// parameters and the index of [`rty::ParamType`](super::ParamType) is based on type parameters only, giving `f` -// /// the type `&T0`. [`TypeBuilder`](crate::refine::TypeBuilder) takes care of this difference when translating MIR -// /// types to Thrust types. -// #[orderable] -// #[debug_format = "T{}"] -// pub struct TypeParamIdx { } -// } - -// impl std::fmt::Display for TypeParamIdx { -// fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result { -// write!(f, "T{}", self.index()) -// } -// } - -// impl<'a, D> Pretty<'a, D, termcolor::ColorSpec> for &TypeParamIdx -// where -// D: pretty::DocAllocator<'a, termcolor::ColorSpec>, -// { -// fn pretty(self, allocator: &'a D) -> pretty::DocBuilder<'a, D, termcolor::ColorSpec> { -// allocator -// .as_string(self) -// .annotate(TypeParamIdx::color_spec()) -// } -// } - -// impl TypeParamIdx { -// fn color_spec() -> termcolor::ColorSpec { -// termcolor::ColorSpec::new() -// } -// } +rustc_index::newtype_index! { + /// An index representing a type parameter. + /// + /// ## Note on indexing of type parameters + /// + /// The index of [`rustc_middle::ty::ParamTy`] is based on all generic parameters in + /// the definition, including lifetimes. Given the following definition: + /// + /// ```rust + /// struct X<'a, T> { f: &'a T } + /// ``` + /// + /// The type of field `f` is `&T1` (not `&T0`) in MIR. However, in Thrust, we ignore lifetime + /// parameters and the index of [`rty::ParamType`](super::ParamType) is based on type parameters only, giving `f` + /// the type `&T0`. [`TypeBuilder`](crate::refine::TypeBuilder) takes care of this difference when translating MIR + /// types to Thrust types. + #[orderable] + #[debug_format = "T{}"] + pub struct TypeParamIdx { } +} + +impl std::fmt::Display for TypeParamIdx { + fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result { + write!(f, "T{}", self.index()) + } +} + +impl<'a, D> Pretty<'a, D, termcolor::ColorSpec> for &TypeParamIdx +where + D: pretty::DocAllocator<'a, termcolor::ColorSpec>, +{ + fn pretty(self, allocator: &'a D) -> pretty::DocBuilder<'a, D, termcolor::ColorSpec> { + allocator + .as_string(self) + .annotate(TypeParamIdx::color_spec()) + } +} + +impl TypeParamIdx { + fn color_spec() -> termcolor::ColorSpec { + termcolor::ColorSpec::new() + } +} pub type RefinedTypeArgs = IndexVec>; pub type TypeArgs = IndexVec>; diff --git a/src/rty/subtyping.rs b/src/rty/subtyping.rs index 3b9cae09..72ec7df6 100644 --- a/src/rty/subtyping.rs +++ b/src/rty/subtyping.rs @@ -123,7 +123,8 @@ where let cs2 = self.relate_sub_refined_type(&got.elem, &expected.elem); clauses.extend(cs2); } - (Type::Param(got), Type::Param(expected)) if got.idx == expected.idx => {} + (Type::Param(got), Type::Param(expected)) + if got.forall_sort_idx == expected.forall_sort_idx => {} _ => panic!( "inconsistent types: got={}, expected={}", got.display(), From 2855cc57643307b68c2c344c062f6b05265e7a0d Mon Sep 17 00:00:00 2001 From: coeff-aij <175928954+coeff-aij@users.noreply.github.com> Date: Sat, 30 May 2026 18:04:51 +0900 Subject: [PATCH 023/142] add: insert declarations of universally quantified predicate variables using (declare-forall-fun) --- src/analyze/annot_fn.rs | 14 +++++++++++++- src/chc.rs | 7 +++++++ src/chc/smtlib2.rs | 32 ++++++++++++++++++++++++++++++++ src/chc/unbox.rs | 11 +++++++++++ 4 files changed, 63 insertions(+), 1 deletion(-) diff --git a/src/analyze/annot_fn.rs b/src/analyze/annot_fn.rs index a9c6f234..3b3e44e8 100644 --- a/src/analyze/annot_fn.rs +++ b/src/analyze/annot_fn.rs @@ -672,8 +672,20 @@ impl<'a, 'tcx> AnnotFnTranslator<'a, 'tcx> { def_id }; + let typeck_results = self.tcx.typeck(self.local_def_id); let pred = if is_unresolved_args { - refine::forall_pred(self.tcx, pred_def_id).into() + let pred = refine::forall_pred(self.tcx, pred_def_id); + let sig = args + .iter() + .map(|e| { + let ty = typeck_results.expr_ty(e); + self.type_builder.build(ty).to_sort() + }) + .collect(); + self.system + .borrow_mut() + .register_forall_pred(pred.clone(), sig); + pred.into() } else { refine::user_defined_pred(self.tcx, pred_def_id).into() }; diff --git a/src/chc.rs b/src/chc.rs index 88e354da..a07c62c8 100644 --- a/src/chc.rs +++ b/src/chc.rs @@ -1,5 +1,7 @@ //! A multi-sorted CHC system with tuples. +use std::collections::HashMap; + use pretty::{termcolor, Pretty}; use rustc_index::IndexVec; @@ -1875,6 +1877,7 @@ pub struct System { pub pred_vars: IndexVec, pub forall_sorts: Vec, pub num_forall_sort_idx: ForallSortIdx, + forall_pred_vars: HashMap, } impl System { @@ -1882,6 +1885,10 @@ impl System { self.pred_vars.push(PredVarDef { sig, debug_info }) } + pub fn register_forall_pred(&mut self, pred: ForallPred, sig: PredSig) { + self.forall_pred_vars.entry(pred).or_insert(sig); + } + pub fn new_forall_sort(&mut self) -> ForallSortIdx { let new_idx = self.num_forall_sort_idx; self.num_forall_sort_idx += 1; diff --git a/src/chc/smtlib2.rs b/src/chc/smtlib2.rs index 617c1ac7..aab762bf 100644 --- a/src/chc/smtlib2.rs +++ b/src/chc/smtlib2.rs @@ -589,6 +589,34 @@ impl<'ctx, 'a> UserDefinedPredDef<'ctx, 'a> { Self { ctx, inner } } } + +pub struct ForallPredDef<'ctx, 'a> { + ctx: &'ctx FormatContext, + symbol: &'a chc::ForallPred, + sig: &'a chc::PredSig, +} + +impl<'ctx, 'a> std::fmt::Display for ForallPredDef<'ctx, 'a> { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + let params = List::closed(self.sig.iter().map(|sort| self.ctx.fmt_sort(sort))); + write!( + f, + "(declare-forall-fun {name} {params} Bool)", + name = self.symbol, + ) + } +} + +impl<'ctx, 'a> ForallPredDef<'ctx, 'a> { + pub fn new( + ctx: &'ctx FormatContext, + symbol: &'a chc::ForallPred, + sig: &'a chc::PredSig, + ) -> Self { + Self { ctx, symbol, sig } + } +} + /// A wrapper around a [`chc::System`] that provides a [`std::fmt::Display`] implementation in SMT-LIB2 format. #[derive(Debug, Clone)] pub struct System<'a> { @@ -604,6 +632,10 @@ impl<'a> std::fmt::Display for System<'a> { writeln!(f, "(declare-forall-sort {})\n", forall_sort_idx)?; } + for (symbol, sig) in &self.inner.forall_pred_vars { + writeln!(f, "{}\n", ForallPredDef::new(&self.ctx, symbol, sig))?; + } + writeln!(f, "{}\n", Datatypes::new(&self.ctx, self.ctx.datatypes()))?; for datatype in self.ctx.datatypes() { writeln!(f, "{}", DatatypeDiscrFun::new(&self.ctx, datatype))?; diff --git a/src/chc/unbox.rs b/src/chc/unbox.rs index edf3f854..3f856e48 100644 --- a/src/chc/unbox.rs +++ b/src/chc/unbox.rs @@ -164,6 +164,11 @@ fn unbox_user_defined_pred_def(user_defined_pred_def: UserDefinedPredDef) -> Use UserDefinedPredDef { symbol, sig, body } } +fn unbox_forall_pred_var_def((pred, sig): (ForallPred, PredSig)) -> (ForallPred, PredSig) { + let sig = sig.into_iter().map(unbox_sort).collect(); + (pred, sig) +} + /// Remove all `Box` sorts and `Box`/`BoxCurrent` terms from the system. /// /// The box values in Thrust represent an owned pointer, but are logically equivalent to the inner type. @@ -178,6 +183,7 @@ pub fn unbox(system: System) -> System { pred_vars, forall_sorts, num_forall_sort_idx, + forall_pred_vars, } = system; let datatypes = datatypes.into_iter().map(unbox_datatype).collect(); let clauses = clauses.into_iter().map(unbox_clause).collect(); @@ -186,6 +192,10 @@ pub fn unbox(system: System) -> System { .into_iter() .map(unbox_user_defined_pred_def) .collect(); + let forall_pred_vars = forall_pred_vars + .into_iter() + .map(unbox_forall_pred_var_def) + .collect(); System { raw_commands, datatypes, @@ -194,5 +204,6 @@ pub fn unbox(system: System) -> System { pred_vars, forall_sorts, num_forall_sort_idx, + forall_pred_vars, } } From 81274967dce0f40b93bd5854b656a6f613435fc0 Mon Sep 17 00:00:00 2001 From: coeff-aij <175928954+coeff-aij@users.noreply.github.com> Date: Sun, 31 May 2026 03:11:11 +0900 Subject: [PATCH 024/142] add: analyze and output dependencies between predicates --- src/chc.rs | 107 ++++++++++++++++++++++++++++++++++++++++++++- src/chc/smtlib2.rs | 65 +++++++++++++++++++++++---- 2 files changed, 163 insertions(+), 9 deletions(-) diff --git a/src/chc.rs b/src/chc.rs index a07c62c8..f31a553e 100644 --- a/src/chc.rs +++ b/src/chc.rs @@ -1,6 +1,7 @@ //! A multi-sorted CHC system with tuples. -use std::collections::HashMap; +use std::collections::{HashMap, HashSet}; +use std::hash::Hash; use pretty::{termcolor, Pretty}; use rustc_index::IndexVec; @@ -1192,6 +1193,19 @@ impl Pred { } } +impl TryFrom for ForallPred { + type Error = String; + fn try_from(value: Pred) -> Result { + if let Pred::ForallPred(forall_pred) = value { + Ok(forall_pred) + } else { + Err(format!( + "expected the variant `Pred::ForallPred`, got {value:#?}." + )) + } + } +} + /// An atom is a predicate applied to a list of terms. #[derive(Debug, Clone)] pub struct Atom { @@ -1867,6 +1881,33 @@ pub struct UserDefinedPredDef { body: String, } +pub fn compute_transitive_closure(direct_deps: &HashMap>) -> HashMap> +where + T: Clone + Eq + Hash, +{ + let mut closure = HashMap::new(); + + for start_id in direct_deps.keys() { + let mut visited = HashSet::new(); + let mut stack = vec![start_id.clone()]; + + // Search by DFS + while let Some(current_id) = stack.pop() { + if let Some(deps) = direct_deps.get(¤t_id) { + for next_id in deps { + if visited.insert(next_id.clone()) { + stack.push(next_id.clone()); + } + } + } + } + + closure.insert(start_id.clone(), visited); + } + + closure +} + /// A CHC system. #[derive(Debug, Clone, Default)] pub struct System { @@ -1918,6 +1959,70 @@ impl System { Some(self.clauses.push(clause)) } + fn compute_forall_dependency(clause: &Clause) -> HashSet { + clause + .body + .iter_atoms() + .filter_map(|atom| atom.pred.clone().try_into().ok()) + .collect() + } + + fn compute_exists_dependency(clause: &Clause) -> HashSet { + clause + .body + .iter_atoms() + .filter_map(|atom| match atom.pred { + Pred::Var(id) => Some(id), + _ => None, + }) + .collect() + } + + fn compute_dependency(&self) -> HashMap> { + let mut exists_deps: HashMap> = HashMap::new(); + let mut forall_deps: HashMap> = HashMap::new(); + + for (clause_idx, clause) in self.clauses.iter_enumerated() { + let Pred::Var(head_id) = clause.head.pred else { + continue; + }; + + let exists = Self::compute_exists_dependency(clause); + let forall = Self::compute_forall_dependency(clause); + + tracing::debug!( + "exists deps for {:?} at {:?}: {:?}", + head_id, + clause_idx, + exists + ); + + exists_deps.entry(head_id).or_default().extend(exists); + forall_deps.entry(head_id).or_default().extend(forall); + } + tracing::debug!("direct forall dependencies: {:#?}", forall_deps); + tracing::debug!("direct exists dependencies: {:#?}", exists_deps); + + let transitive_exists_deps = compute_transitive_closure(&exists_deps); + tracing::debug!("transitive exists dependencies: {:#?}", exists_deps); + + let mut propagated_forall_deps = HashMap::new(); + + for (pred, reachable_preds) in transitive_exists_deps { + let mut deps = forall_deps.get(&pred).cloned().unwrap_or_default(); + + for reachable in reachable_preds { + if let Some(foralls) = forall_deps.get(&reachable) { + deps.extend(foralls.iter().cloned()); + } + } + + propagated_forall_deps.insert(pred, deps); + } + + propagated_forall_deps + } + pub fn smtlib2(&self) -> smtlib2::System<'_> { smtlib2::System::new(self) } diff --git a/src/chc/smtlib2.rs b/src/chc/smtlib2.rs index aab762bf..6546ffac 100644 --- a/src/chc/smtlib2.rs +++ b/src/chc/smtlib2.rs @@ -6,6 +6,8 @@ //! such as naming convention and solver-specific workarounds. //! The output of this module is what gets passed to the external CHC solver. +use std::collections::HashSet; + use crate::chc::{self, format_context::FormatContext}; /// A helper struct to display a list of items. @@ -617,6 +619,44 @@ impl<'ctx, 'a> ForallPredDef<'ctx, 'a> { } } +pub struct DepExistsPredVarDef<'ctx, 'a> { + ctx: &'ctx FormatContext, + id: &'a chc::PredVarId, + def: &'a chc::PredVarDef, + dependencies: &'a HashSet, +} + +impl<'ctx, 'a> std::fmt::Display for DepExistsPredVarDef<'ctx, 'a> { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + if !self.def.debug_info.is_empty() { + writeln!(f, "{}", self.def.debug_info.display("; "))?; + } + writeln!( + f, + "(declare-dep-exists-fun {} {} {} Bool)", + self.id, + List::closed(self.dependencies), + List::closed(self.def.sig.iter().map(|s| self.ctx.fmt_sort(s))), + ) + } +} + +impl<'ctx, 'a> DepExistsPredVarDef<'ctx, 'a> { + pub fn new( + ctx: &'ctx FormatContext, + id: &'a chc::PredVarId, + def: &'a chc::PredVarDef, + dependencies: &'a HashSet, + ) -> Self { + Self { + ctx, + id, + def, + dependencies, + } + } +} + /// A wrapper around a [`chc::System`] that provides a [`std::fmt::Display`] implementation in SMT-LIB2 format. #[derive(Debug, Clone)] pub struct System<'a> { @@ -656,16 +696,25 @@ impl<'a> std::fmt::Display for System<'a> { } writeln!(f)?; + let dependencies = self.inner.compute_dependency(); for (p, def) in self.inner.pred_vars.iter_enumerated() { - if !def.debug_info.is_empty() { - writeln!(f, "{}", def.debug_info.display("; "))?; + if dependencies.contains_key(&p) && !dependencies[&p].is_empty() { + writeln!( + f, + "{}\n", + DepExistsPredVarDef::new(&self.ctx, &p, def, &dependencies[&p]) + )?; + } else { + if !def.debug_info.is_empty() { + writeln!(f, "{}", def.debug_info.display("; "))?; + } + writeln!( + f, + "(declare-fun {} {} Bool)\n", + p, + List::closed(def.sig.iter().map(|s| self.ctx.fmt_sort(s))) + )?; } - writeln!( - f, - "(declare-fun {} {} Bool)\n", - p, - List::closed(def.sig.iter().map(|s| self.ctx.fmt_sort(s))) - )?; } for (id, clause) in self.inner.clauses.iter_enumerated() { writeln!( From 23187ce4217f52a0d6d3278062536c5d59522116 Mon Sep 17 00:00:00 2001 From: coeff-aij <175928954+coeff-aij@users.noreply.github.com> Date: Wed, 3 Jun 2026 21:14:37 +0900 Subject: [PATCH 025/142] add: translate alias type into forall sort --- src/refine/template.rs | 10 +++++++++- src/rty.rs | 42 ++++++++++++++++++++++++++++++++++++++++-- 2 files changed, 49 insertions(+), 3 deletions(-) diff --git a/src/refine/template.rs b/src/refine/template.rs index e283b06d..5382353a 100644 --- a/src/refine/template.rs +++ b/src/refine/template.rs @@ -140,6 +140,14 @@ impl<'tcx> TypeBuilder<'tcx> { rty::ParamType::new(param_local_idx, *forall_sort_idx).into() } + fn translate_alias_type(&self, ty: &mir_ty::AliasTy) -> rty::Type { + let mut type_params = self.type_params.borrow_mut(); + let index = type_params + .entry(TypeParam::AssocType(ty.def_id)) + .or_insert_with(|| self.system.borrow_mut().new_forall_sort()); + rty::AliasType::new(*index).into() + } + /// Replaces {closure} types with thrust_models::Closure<{closure}>. /// /// Ideally, we want to have `impl Model for F where F: Fn` instead of this and let @@ -301,7 +309,7 @@ impl<'tcx> TypeBuilder<'tcx> { unimplemented!("unsupported ADT: {:?}", ty); } } - // mir_ty::TyKind::Alias(_, ty) => self.translate_alias_type(ty), + mir_ty::TyKind::Alias(_, ty) => self.translate_alias_type(ty), kind => unimplemented!("unrefined_ty: {:?}", kind), } } diff --git a/src/rty.rs b/src/rty.rs index dcf9576f..da781836 100644 --- a/src/rty.rs +++ b/src/rty.rs @@ -763,6 +763,30 @@ impl ParamType { } } +#[derive(Debug, Clone)] +pub struct AliasType { + forall_sort_idx: ForallSortIdx, +} + +impl<'a, D> Pretty<'a, D, termcolor::ColorSpec> for &AliasType +where + D: pretty::DocAllocator<'a, termcolor::ColorSpec>, +{ + fn pretty(self, allocator: &'a D) -> pretty::DocBuilder<'a, D, termcolor::ColorSpec> { + self.forall_sort_idx.pretty(allocator) + } +} + +impl AliasType { + pub fn new(forall_sort_idx: ForallSortIdx) -> Self { + AliasType { forall_sort_idx } + } + + pub fn forall_sort_index(&self) -> ForallSortIdx { + self.forall_sort_idx + } +} + /// An array type. #[derive(Debug, Clone)] pub struct ArrayType { @@ -854,6 +878,7 @@ pub enum Type { String, Never, Param(ParamType), + Alias(AliasType), Pointer(PointerType), Function(FunctionType), Tuple(TupleType), @@ -867,6 +892,12 @@ impl From for Type { } } +impl From for Type { + fn from(t: AliasType) -> Type { + Type::Alias(t) + } +} + impl From for Type { fn from(t: FunctionType) -> Type { Type::Function(t) @@ -910,6 +941,7 @@ where Type::String => allocator.text("string"), Type::Never => allocator.text("!"), Type::Param(ty) => ty.pretty(allocator), + Type::Alias(ty) => ty.pretty(allocator), Type::Pointer(ty) => ty.pretty(allocator), Type::Function(ty) => ty.pretty(allocator), Type::Tuple(ty) => ty.pretty(allocator), @@ -1043,6 +1075,7 @@ impl Type { Type::String => chc::Sort::null(), Type::Never => chc::Sort::null(), Type::Param(ty) => chc::Sort::forall(ty.forall_sort_index()), + Type::Alias(ty) => chc::Sort::Forall(ty.forall_sort_index()), Type::Pointer(ty) => { let elem_sort = ty.elem.ty.to_sort(); @@ -1080,6 +1113,7 @@ impl Type { Type::String => Type::String, Type::Never => Type::Never, Type::Param(ty) => Type::Param(ty), + Type::Alias(ty) => Type::Alias(ty), Type::Pointer(ty) => Type::Pointer(ty.subst_var(f)), Type::Function(ty) => Type::Function(ty), Type::Tuple(ty) => Type::Tuple(ty.subst_var(f)), @@ -1098,6 +1132,7 @@ impl Type { Type::String => Type::String, Type::Never => Type::Never, Type::Param(ty) => Type::Param(ty), + Type::Alias(ty) => Type::Alias(ty), Type::Pointer(ty) => Type::Pointer(ty.map_var(f)), Type::Function(ty) => Type::Function(ty), Type::Tuple(ty) => Type::Tuple(ty.map_var(f)), @@ -1117,6 +1152,7 @@ impl Type { Type::String => Type::String, Type::Never => Type::Never, Type::Param(ty) => Type::Param(ty), + Type::Alias(ty) => Type::Alias(ty), Type::Pointer(ty) => Type::Pointer(ty.strip_refinement()), Type::Function(ty) => Type::Function(ty), Type::Tuple(ty) => Type::Tuple(ty.strip_refinement()), @@ -1127,7 +1163,9 @@ impl Type { pub fn free_ty_params(&self) -> HashSet { match self { - Type::Int | Type::Bool | Type::String | Type::Never => Default::default(), + Type::Int | Type::Bool | Type::String | Type::Never | Type::Alias(_) => { + Default::default() + } Type::Param(ty) => std::iter::once(ty.type_param_index()).collect(), Type::Pointer(ty) => ty.free_ty_params(), Type::Function(ty) => ty.free_ty_params(), @@ -1693,7 +1731,7 @@ impl RefinedType { { self.refinement.subst_ty_params_in_sorts(subst); match &mut self.ty { - Type::Int | Type::Bool | Type::String | Type::Never => {} + Type::Int | Type::Bool | Type::String | Type::Never | Type::Alias(_) => {} Type::Param(ty) => { if let Some(rty) = subst.get(ty.type_param_index()) { let RefinedType { From 01f9f6cf026c16b3c311f58a1b4c12af79740b26 Mon Sep 17 00:00:00 2001 From: coeff-aij <175928954+coeff-aij@users.noreply.github.com> Date: Wed, 3 Jun 2026 21:16:16 +0900 Subject: [PATCH 026/142] add: tests for unknown type parameters --- tests/ui/pass/traits/simple_loop_call.rs | 45 ++++++++++++++++++++++++ tests/ui/pass/traits/simple_loop_self.rs | 28 +++++++++++++++ 2 files changed, 73 insertions(+) create mode 100644 tests/ui/pass/traits/simple_loop_call.rs create mode 100644 tests/ui/pass/traits/simple_loop_self.rs diff --git a/tests/ui/pass/traits/simple_loop_call.rs b/tests/ui/pass/traits/simple_loop_call.rs new file mode 100644 index 00000000..de2f0e82 --- /dev/null +++ b/tests/ui/pass/traits/simple_loop_call.rs @@ -0,0 +1,45 @@ +//@check-pass +//@compile-flags: -C debug-assertions=off +//@rustc-env: THRUST_SOLVER=tests/thrust-pcsat-wrapper THRUST_SOLVER_TIMEOUT_SECS=60 + +#[thrust_macros::context] +trait A { + #[thrust_macros::requires(Self::p(x))] + #[thrust_macros::ensures(Self::p(result))] + fn f(&self, x: i64) -> i64; + + #[thrust_macros::predicate] + fn p(x: i64) -> bool; +} + +#[thrust_macros::requires(T::p(x))] +#[thrust_macros::ensures(T::p(result))] +fn target(a: &T, x: i64) -> i64 { + let mut v = x; + let mut i = 0; + while i < 3 { + v = a.f(v); + i += 1; + } + + v +} + +struct B(i64); + +impl A for B { + #[thrust_macros::requires(Self::p(x))] + #[thrust_macros::ensures(Self::p(result))] + fn f(&self, x: i64) -> i64{ + x + } + + #[thrust_macros::predicate] + fn p(x: i64) -> bool { + "(> x 0)"; true + } +} + +fn main() { + +} diff --git a/tests/ui/pass/traits/simple_loop_self.rs b/tests/ui/pass/traits/simple_loop_self.rs new file mode 100644 index 00000000..0c026e06 --- /dev/null +++ b/tests/ui/pass/traits/simple_loop_self.rs @@ -0,0 +1,28 @@ +//@check-pass +//@compile-flags: -C debug-assertions=off +//@rustc-env: THRUST_SOLVER=tests/thrust-pcsat-wrapper THRUST_SOLVER_TIMEOUT_SECS=60 + +#[thrust_macros::context] +trait A { + #[thrust_macros::requires(Self::p(*self, x))] + #[thrust_macros::ensures(Self::p(*self, result))] + fn f(&self, x: i64) -> i64; + + #[thrust_macros::predicate] + fn p(self, x: i64) -> bool; +} + +#[thrust_macros::requires(T::p(*a, x))] +#[thrust_macros::ensures(T::p(*a, result))] +fn target(a: &T, x: i64) -> i64 { + let mut v = x; + let mut i = 0; + while i < 3 { + v = a.f(v); + i += 1; + } + + v +} + +fn main() {} From 743bcdaa7c59d201dcef2ced448ca746ad469dea Mon Sep 17 00:00:00 2001 From: coeff-aij <175928954+coeff-aij@users.noreply.github.com> Date: Mon, 8 Jun 2026 15:39:43 +0900 Subject: [PATCH 027/142] fix: propagate owner_fn_id of type parameters --- src/analyze/annot_fn.rs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/analyze/annot_fn.rs b/src/analyze/annot_fn.rs index 3b3e44e8..97dacd98 100644 --- a/src/analyze/annot_fn.rs +++ b/src/analyze/annot_fn.rs @@ -682,7 +682,8 @@ impl<'a, 'tcx> AnnotFnTranslator<'a, 'tcx> { self.type_builder.build(ty).to_sort() }) .collect(); - self.system + self.analyzer + .system .borrow_mut() .register_forall_pred(pred.clone(), sig); pred.into() From d5cdefb0e874bbb7eaab72a860a18e5aca3ce5c9 Mon Sep 17 00:00:00 2001 From: coeff-aij <175928954+coeff-aij@users.noreply.github.com> Date: Thu, 4 Jun 2026 00:24:40 +0900 Subject: [PATCH 028/142] add: debug print for AliasTy --- src/refine/template.rs | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/src/refine/template.rs b/src/refine/template.rs index 5382353a..5b5cf58f 100644 --- a/src/refine/template.rs +++ b/src/refine/template.rs @@ -144,7 +144,16 @@ impl<'tcx> TypeBuilder<'tcx> { let mut type_params = self.type_params.borrow_mut(); let index = type_params .entry(TypeParam::AssocType(ty.def_id)) - .or_insert_with(|| self.system.borrow_mut().new_forall_sort()); + .or_insert_with(|| { + let idx = self.system.borrow_mut().new_forall_sort(); + tracing::debug!( + "issue the new ForallSortIdx {} for AliasTy {:?}.", + idx, + ty, + ); + idx + }); + rty::AliasType::new(*index).into() } From 0dc245d2d299fe5600741d9e343f3e18fbae542f Mon Sep 17 00:00:00 2001 From: coeff-aij <175928954+coeff-aij@users.noreply.github.com> Date: Mon, 8 Jun 2026 15:41:12 +0900 Subject: [PATCH 029/142] fix: propagate owner_fn_id of type parameters --- src/analyze/annot_fn.rs | 9 ++++++++- src/refine/template.rs | 8 ++------ 2 files changed, 10 insertions(+), 7 deletions(-) diff --git a/src/analyze/annot_fn.rs b/src/analyze/annot_fn.rs index 97dacd98..9705528b 100644 --- a/src/analyze/annot_fn.rs +++ b/src/analyze/annot_fn.rs @@ -243,7 +243,9 @@ impl<'a, 'tcx> AnnotFnTranslator<'a, 'tcx> { .instantiate_generics(ty, self.generic_args) .unwrap_or(ty); let typing_env = mir_ty::TypingEnv::fully_monomorphized(); - self.tcx.normalize_erasing_regions(typing_env, instantiated) + self.tcx + .try_normalize_erasing_regions(typing_env, instantiated) + .unwrap_or(instantiated) } fn pat_ty(&self, pat: &'tcx rustc_hir::Pat<'tcx>) -> mir_ty::Ty<'tcx> { @@ -682,6 +684,11 @@ impl<'a, 'tcx> AnnotFnTranslator<'a, 'tcx> { self.type_builder.build(ty).to_sort() }) .collect(); + tracing::debug!( + "register ForallPred {:?} with signature {:?}", + pred, + sig + ); self.analyzer .system .borrow_mut() diff --git a/src/refine/template.rs b/src/refine/template.rs index 5b5cf58f..adf5baff 100644 --- a/src/refine/template.rs +++ b/src/refine/template.rs @@ -146,14 +146,10 @@ impl<'tcx> TypeBuilder<'tcx> { .entry(TypeParam::AssocType(ty.def_id)) .or_insert_with(|| { let idx = self.system.borrow_mut().new_forall_sort(); - tracing::debug!( - "issue the new ForallSortIdx {} for AliasTy {:?}.", - idx, - ty, - ); + tracing::debug!("issue the new ForallSortIdx {} for AliasTy {:?}.", idx, ty,); idx }); - + rty::AliasType::new(*index).into() } From 5d5026458460ecbd5cb835ef7ef41a662fd6f71c Mon Sep 17 00:00:00 2001 From: coeff-aij <175928954+coeff-aij@users.noreply.github.com> Date: Thu, 4 Jun 2026 20:09:31 +0900 Subject: [PATCH 030/142] add: translate ::Ty into ParamTy --- src/refine/template.rs | 20 ++++++++++++++++++-- 1 file changed, 18 insertions(+), 2 deletions(-) diff --git a/src/refine/template.rs b/src/refine/template.rs index adf5baff..369ff4c5 100644 --- a/src/refine/template.rs +++ b/src/refine/template.rs @@ -208,7 +208,11 @@ impl<'tcx> TypeBuilder<'tcx> { .tcx .try_normalize_erasing_regions(self.typing_env, projection_ty) { - tracing::debug!("the type {:#?} is resolved as the type {:#?}.", orig_ty, ty); + tracing::debug!( + "the type {:#?} is normalized as the type {:#?}.", + orig_ty, + normalized_ty + ); let contains_model_ty_alias = normalized_ty.walk().any(|arg| { if let mir_ty::GenericArgKind::Type(t) = arg.kind() { matches!(t.kind(), mir_ty::TyKind::Alias(_, alias_ty) if alias_ty.def_id == model_ty_def_id) @@ -314,7 +318,19 @@ impl<'tcx> TypeBuilder<'tcx> { unimplemented!("unsupported ADT: {:?}", ty); } } - mir_ty::TyKind::Alias(_, ty) => self.translate_alias_type(ty), + mir_ty::TyKind::Alias(mir_ty::AliasTyKind::Projection, ty) => { + if let Some(model_ty_def_id) = self.def_ids.model_ty() { + let arg_ty = ty.args.type_at(0); + + if ty.def_id == model_ty_def_id + && matches!(arg_ty.kind(), mir_ty::TyKind::Param(_)) + { + return self.build(arg_ty); + } + } + + self.translate_alias_type(ty) + } kind => unimplemented!("unrefined_ty: {:?}", kind), } } From 516cbd522f4926ba5b53f8b4a868b555027c16b7 Mon Sep 17 00:00:00 2001 From: coeff-aij <175928954+coeff-aij@users.noreply.github.com> Date: Mon, 8 Jun 2026 12:34:30 +0900 Subject: [PATCH 031/142] add: register_generic_def() for substitution of generic args from call site --- src/analyze.rs | 76 +++++++++++++++++++++++++++++----------- src/analyze/crate_.rs | 9 ++++- src/analyze/local_def.rs | 5 +++ 3 files changed, 68 insertions(+), 22 deletions(-) diff --git a/src/analyze.rs b/src/analyze.rs index 439b1456..3bd42386 100644 --- a/src/analyze.rs +++ b/src/analyze.rs @@ -160,9 +160,18 @@ struct DeferredDefTy<'tcx> { mode: DeferredDefMode, } +#[derive(Debug, Clone)] +struct GenericDefTy<'tcx> { + // this is different from a key in defs when the def is extern_spec_fn + local_def_id: LocalDefId, + cache: Rc, rty::RefinedType>>>, + rty: rty::RefinedType, +} + #[derive(Debug, Clone)] enum DefTy<'tcx> { Concrete(rty::RefinedType), + Generic(GenericDefTy<'tcx>), Deferred(DeferredDefTy<'tcx>), } @@ -408,9 +417,27 @@ impl<'tcx> Analyzer<'tcx> { }); } + pub fn register_generic_def( + &mut self, + target_def_id: DefId, + local_def_id: LocalDefId, + rty: rty::RefinedType, + ) { + tracing::info!(?target_def_id, ?local_def_id, rty = %rty.display(), "register_generic_def"); + self.defs.insert( + target_def_id, + DefTy::Generic(GenericDefTy { + rty, + local_def_id, + cache: Rc::new(RefCell::new(HashMap::new())), + }), + ); + } + pub fn concrete_def_ty(&self, def_id: DefId) -> Option<&rty::RefinedType> { self.defs.get(&def_id).and_then(|def_ty| match def_ty { DefTy::Concrete(rty) => Some(rty), + DefTy::Generic(GenericDefTy { rty, .. }) => Some(rty), DefTy::Deferred(_) => None, }) } @@ -432,6 +459,7 @@ impl<'tcx> Analyzer<'tcx> { ); let mut def_ty = match self.defs.get(&def_id)? { DefTy::Concrete(rty) => rty.clone(), + DefTy::Generic(generic) => generic.cache.borrow().get(&generic_args)?.clone(), DefTy::Deferred(deferred) => deferred.cache.borrow().get(&generic_args)?.clone(), }; def_ty.instantiate_ty_params( @@ -477,29 +505,35 @@ impl<'tcx> Analyzer<'tcx> { ) -> Option { let type_builder = self.type_builder(self.def_ids(), caller_def_id); - let deferred_ty = match self.defs.get(&def_id)? { - DefTy::Concrete(rty) => { - let mut def_ty = rty.clone(); - def_ty.instantiate_ty_params( - generic_args - .types() - .map(|ty| type_builder.build(ty)) - .map(rty::RefinedType::unrefined) - .collect(), - ); - return Some(def_ty); - } - DefTy::Deferred(deferred) => deferred, - }; + let (local_def_id, instantiated_ty_cache, deferred_ty_mode) = + match self.defs.get(&def_id)? { + DefTy::Concrete(rty) => { + let mut def_ty = rty.clone(); + def_ty.instantiate_ty_params( + generic_args + .types() + .map(|ty| type_builder.build(ty)) + .map(rty::RefinedType::unrefined) + .collect(), + ); + return Some(def_ty); + } + DefTy::Generic(generic) => (generic.local_def_id, Rc::clone(&generic.cache), None), + DefTy::Deferred(deferred) => ( + deferred.local_def_id, + Rc::clone(&deferred.cache), + Some(deferred.mode), + ), + }; - let deferred_ty_cache = Rc::clone(&deferred_ty.cache); // to cut reference to allow &mut self - if let Some(rty) = deferred_ty_cache.borrow().get(&generic_args) { + if let Some(rty) = instantiated_ty_cache.borrow().get(&generic_args) { return Some(rty.clone()); } - let deferred_ty_mode = deferred_ty.mode; - let mut analyzer = self.local_def_analyzer(deferred_ty.local_def_id); - analyzer.generic_args(generic_args); + let mut analyzer = self.local_def_analyzer(local_def_id); + analyzer + .owner_fn_id(caller_def_id) + .generic_args(generic_args); let mut expected = analyzer.expected_ty(); // parameters in annotations are left as params @@ -511,12 +545,12 @@ impl<'tcx> Analyzer<'tcx> { .map(rty::RefinedType::unrefined) .collect(), ); - deferred_ty_cache + instantiated_ty_cache .borrow_mut() .insert(generic_args, expected.clone()); tracing::info!(?def_id, rty = %expected.display(), ?generic_args, "deferred def"); - if deferred_ty_mode.should_analyze() { + if deferred_ty_mode.is_some_and(|mode| mode.should_analyze()) { let mut body_analyzer = if analyzer.local_def_id().to_def_id() == def_id { analyzer } else { diff --git a/src/analyze/crate_.rs b/src/analyze/crate_.rs index 58bd309e..bec659e3 100644 --- a/src/analyze/crate_.rs +++ b/src/analyze/crate_.rs @@ -69,6 +69,7 @@ impl<'tcx, 'ctx> Analyzer<'tcx, 'ctx> { #[tracing::instrument(skip(self), fields(def_id = %self.tcx.def_path_str(local_def_id)))] fn refine_fn_def(&mut self, local_def_id: LocalDefId) { + let sig = self.ctx.fn_sig(local_def_id.to_def_id()); let mut analyzer = self.ctx.local_def_analyzer(local_def_id); if analyzer.is_annotated_as_trusted() { @@ -113,7 +114,13 @@ impl<'tcx, 'ctx> Analyzer<'tcx, 'ctx> { let owner_fn_id = analyzer.owner_fn_id; let expected = analyzer.expected_ty(); - self.ctx.register_def(owner_fn_id, expected); + use mir_ty::TypeVisitableExt as _; + if sig.has_param() { + self.ctx + .register_generic_def(owner_fn_id, local_def_id, expected); + } else { + self.ctx.register_def(owner_fn_id, expected); + } } fn analyze_local_defs(&mut self) { diff --git a/src/analyze/local_def.rs b/src/analyze/local_def.rs index 6f717ef8..7e912627 100644 --- a/src/analyze/local_def.rs +++ b/src/analyze/local_def.rs @@ -1231,6 +1231,11 @@ impl<'tcx, 'ctx> Analyzer<'tcx, 'ctx> { self.local_def_id } + pub fn owner_fn_id(&mut self, owner_fn_id: DefId) -> &mut Self { + self.owner_fn_id = owner_fn_id; + self + } + pub fn generic_args(&mut self, generic_args: mir_ty::GenericArgsRef<'tcx>) -> &mut Self { self.generic_args = generic_args; self.body = From 6de74be128e656c91739f7aeb1a6a12e2fb449ce Mon Sep 17 00:00:00 2001 From: coeff-aij <175928954+coeff-aij@users.noreply.github.com> Date: Sun, 7 Jun 2026 23:36:32 +0900 Subject: [PATCH 032/142] fix: instantiate generic type parameters contained in args of predicate calls --- src/analyze/annot_fn.rs | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/analyze/annot_fn.rs b/src/analyze/annot_fn.rs index 9705528b..c1872560 100644 --- a/src/analyze/annot_fn.rs +++ b/src/analyze/annot_fn.rs @@ -681,6 +681,9 @@ impl<'a, 'tcx> AnnotFnTranslator<'a, 'tcx> { .iter() .map(|e| { let ty = typeck_results.expr_ty(e); + let ty = self + .instantiate_generics(ty, generic_args) + .unwrap_or(ty); self.type_builder.build(ty).to_sort() }) .collect(); From b921e2cad5116f66ab2c958039d0694a35b4b7dd Mon Sep 17 00:00:00 2001 From: coeff-aij <175928954+coeff-aij@users.noreply.github.com> Date: Mon, 8 Jun 2026 12:48:13 +0900 Subject: [PATCH 033/142] add: distinguish calls of forall-predicates with different type parameters --- src/analyze/annot_fn.rs | 11 +++-------- src/chc.rs | 26 ++++++++++++++++++++------ src/chc/format_context.rs | 5 +++++ src/chc/smtlib2.rs | 23 ++++++++++------------- src/chc/unbox.rs | 6 +++--- src/refine.rs | 6 +++--- 6 files changed, 44 insertions(+), 33 deletions(-) diff --git a/src/analyze/annot_fn.rs b/src/analyze/annot_fn.rs index c1872560..fe88537f 100644 --- a/src/analyze/annot_fn.rs +++ b/src/analyze/annot_fn.rs @@ -676,8 +676,7 @@ impl<'a, 'tcx> AnnotFnTranslator<'a, 'tcx> { let typeck_results = self.tcx.typeck(self.local_def_id); let pred = if is_unresolved_args { - let pred = refine::forall_pred(self.tcx, pred_def_id); - let sig = args + let sig: Vec = args .iter() .map(|e| { let ty = typeck_results.expr_ty(e); @@ -687,15 +686,11 @@ impl<'a, 'tcx> AnnotFnTranslator<'a, 'tcx> { self.type_builder.build(ty).to_sort() }) .collect(); - tracing::debug!( - "register ForallPred {:?} with signature {:?}", - pred, - sig - ); + let pred = refine::forall_pred(self.tcx, pred_def_id, sig); self.analyzer .system .borrow_mut() - .register_forall_pred(pred.clone(), sig); + .register_forall_pred(pred.clone()); pred.into() } else { refine::user_defined_pred(self.tcx, pred_def_id).into() diff --git a/src/chc.rs b/src/chc.rs index f31a553e..53662747 100644 --- a/src/chc.rs +++ b/src/chc.rs @@ -1050,6 +1050,7 @@ impl UserDefinedPred { #[derive(Debug, Clone, PartialEq, Eq, Hash)] pub struct ForallPred { inner: String, + args: Vec, } impl std::fmt::Display for ForallPred { @@ -1061,15 +1062,28 @@ impl std::fmt::Display for ForallPred { impl<'a, D> Pretty<'a, D, termcolor::ColorSpec> for &ForallPred where D: pretty::DocAllocator<'a, termcolor::ColorSpec>, + D::Doc: Clone, { fn pretty(self, allocator: &'a D) -> pretty::DocBuilder<'a, D, termcolor::ColorSpec> { - allocator.text(self.inner.clone()) + let args = allocator.intersperse( + self.args.iter().map(|a| a.pretty(allocator)), + allocator.text(", "), + ); + allocator + .text("forall_pred") + .append( + allocator + .as_string(&self.inner) + .append(args.angles()) + .angles(), + ) + .group() } } impl ForallPred { - pub fn new(inner: String) -> Self { - Self { inner } + pub fn new(inner: String, args: Vec) -> Self { + Self { inner, args } } } @@ -1918,7 +1932,7 @@ pub struct System { pub pred_vars: IndexVec, pub forall_sorts: Vec, pub num_forall_sort_idx: ForallSortIdx, - forall_pred_vars: HashMap, + forall_pred_vars: HashSet, } impl System { @@ -1926,8 +1940,8 @@ impl System { self.pred_vars.push(PredVarDef { sig, debug_info }) } - pub fn register_forall_pred(&mut self, pred: ForallPred, sig: PredSig) { - self.forall_pred_vars.entry(pred).or_insert(sig); + pub fn register_forall_pred(&mut self, pred: ForallPred) { + self.forall_pred_vars.insert(pred); } pub fn new_forall_sort(&mut self) -> ForallSortIdx { diff --git a/src/chc/format_context.rs b/src/chc/format_context.rs index 86895e02..ad54df74 100644 --- a/src/chc/format_context.rs +++ b/src/chc/format_context.rs @@ -348,6 +348,11 @@ impl FormatContext { format!("matcher_pred<{}>", self.fmt_datatype_symbol(sym)) } + pub fn forall_pred(&self, p: &chc::ForallPred) -> impl std::fmt::Display { + let ss = SortSymbols::new(&p.args); + format!("{}{}", p.inner, ss) + } + fn fmt_sort_impl(&self, sort: &chc::Sort) -> Box { match sort { chc::Sort::Array(s1, s2) => { diff --git a/src/chc/smtlib2.rs b/src/chc/smtlib2.rs index 6546ffac..929ff547 100644 --- a/src/chc/smtlib2.rs +++ b/src/chc/smtlib2.rs @@ -234,6 +234,7 @@ impl<'ctx, 'a> std::fmt::Display for Atom<'ctx, 'a> { } let pred = match &self.inner.pred { chc::Pred::Matcher(p) => self.ctx.matcher_pred(p).to_string(), + chc::Pred::ForallPred(p) => self.ctx.forall_pred(p).to_string(), p => p.name().into_owned(), }; if self.inner.args.is_empty() { @@ -594,28 +595,24 @@ impl<'ctx, 'a> UserDefinedPredDef<'ctx, 'a> { pub struct ForallPredDef<'ctx, 'a> { ctx: &'ctx FormatContext, - symbol: &'a chc::ForallPred, - sig: &'a chc::PredSig, + pred: &'a chc::ForallPred, } impl<'ctx, 'a> std::fmt::Display for ForallPredDef<'ctx, 'a> { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - let params = List::closed(self.sig.iter().map(|sort| self.ctx.fmt_sort(sort))); + let params = self.pred.args.iter().map(|sort| self.ctx.fmt_sort(sort)); + let params = List::closed(params); write!( f, "(declare-forall-fun {name} {params} Bool)", - name = self.symbol, + name = self.ctx.forall_pred(self.pred), ) } } impl<'ctx, 'a> ForallPredDef<'ctx, 'a> { - pub fn new( - ctx: &'ctx FormatContext, - symbol: &'a chc::ForallPred, - sig: &'a chc::PredSig, - ) -> Self { - Self { ctx, symbol, sig } + pub fn new(ctx: &'ctx FormatContext, pred: &'a chc::ForallPred) -> Self { + Self { ctx, pred } } } @@ -635,7 +632,7 @@ impl<'ctx, 'a> std::fmt::Display for DepExistsPredVarDef<'ctx, 'a> { f, "(declare-dep-exists-fun {} {} {} Bool)", self.id, - List::closed(self.dependencies), + List::closed(self.dependencies.iter().map(|p| self.ctx.forall_pred(p))), List::closed(self.def.sig.iter().map(|s| self.ctx.fmt_sort(s))), ) } @@ -672,8 +669,8 @@ impl<'a> std::fmt::Display for System<'a> { writeln!(f, "(declare-forall-sort {})\n", forall_sort_idx)?; } - for (symbol, sig) in &self.inner.forall_pred_vars { - writeln!(f, "{}\n", ForallPredDef::new(&self.ctx, symbol, sig))?; + for pred in &self.inner.forall_pred_vars { + writeln!(f, "{}\n", ForallPredDef::new(&self.ctx, pred))?; } writeln!(f, "{}\n", Datatypes::new(&self.ctx, self.ctx.datatypes()))?; diff --git a/src/chc/unbox.rs b/src/chc/unbox.rs index 3f856e48..3b8bd6a8 100644 --- a/src/chc/unbox.rs +++ b/src/chc/unbox.rs @@ -164,9 +164,9 @@ fn unbox_user_defined_pred_def(user_defined_pred_def: UserDefinedPredDef) -> Use UserDefinedPredDef { symbol, sig, body } } -fn unbox_forall_pred_var_def((pred, sig): (ForallPred, PredSig)) -> (ForallPred, PredSig) { - let sig = sig.into_iter().map(unbox_sort).collect(); - (pred, sig) +fn unbox_forall_pred_var_def(pred: ForallPred) -> ForallPred { + let args = pred.args.into_iter().map(unbox_sort).collect(); + ForallPred { args, ..pred } } /// Remove all `Box` sorts and `Box`/`BoxCurrent` terms from the system. diff --git a/src/refine.rs b/src/refine.rs index cbbd37c4..5ecd82c9 100644 --- a/src/refine.rs +++ b/src/refine.rs @@ -18,7 +18,7 @@ pub use env::{ Assumption, EnumDefProvider, Env, PlaceType, PlaceTypeBuilder, PlaceTypeVar, TempVarIdx, Var, }; -use crate::chc::{DatatypeSymbol, ForallPred, UserDefinedPred}; +use crate::chc::{DatatypeSymbol, ForallPred, Sort, UserDefinedPred}; use rustc_middle::ty as mir_ty; use rustc_span::def_id::DefId; @@ -41,6 +41,6 @@ pub fn user_defined_pred(tcx: mir_ty::TyCtxt<'_>, did: DefId) -> UserDefinedPred UserDefinedPred::new(stable_def_id_symbol(tcx, did, "p")) } -pub fn forall_pred(tcx: mir_ty::TyCtxt<'_>, did: DefId) -> ForallPred { - ForallPred::new(stable_def_id_symbol(tcx, did, "q")) +pub fn forall_pred(tcx: mir_ty::TyCtxt<'_>, did: DefId, args: Vec) -> ForallPred { + ForallPred::new(stable_def_id_symbol(tcx, did, "q"), args) } From 59f401c5f5918dd5bb3d776a8d955d55d6f810d2 Mon Sep 17 00:00:00 2001 From: coeff-aij <175928954+coeff-aij@users.noreply.github.com> Date: Mon, 8 Jun 2026 01:44:07 +0900 Subject: [PATCH 034/142] fix: skip expected_ty() for trait methods without MIR body --- src/analyze.rs | 38 ++++++++++++++++++++------------------ src/analyze/crate_.rs | 6 +++++- 2 files changed, 25 insertions(+), 19 deletions(-) diff --git a/src/analyze.rs b/src/analyze.rs index 3bd42386..5dbc0d19 100644 --- a/src/analyze.rs +++ b/src/analyze.rs @@ -165,7 +165,7 @@ struct GenericDefTy<'tcx> { // this is different from a key in defs when the def is extern_spec_fn local_def_id: LocalDefId, cache: Rc, rty::RefinedType>>>, - rty: rty::RefinedType, + rty: Option, } #[derive(Debug, Clone)] @@ -421,9 +421,9 @@ impl<'tcx> Analyzer<'tcx> { &mut self, target_def_id: DefId, local_def_id: LocalDefId, - rty: rty::RefinedType, + rty: Option, ) { - tracing::info!(?target_def_id, ?local_def_id, rty = %rty.display(), "register_generic_def"); + tracing::info!(?target_def_id, ?local_def_id, ?rty, "register_generic_def"); self.defs.insert( target_def_id, DefTy::Generic(GenericDefTy { @@ -437,7 +437,7 @@ impl<'tcx> Analyzer<'tcx> { pub fn concrete_def_ty(&self, def_id: DefId) -> Option<&rty::RefinedType> { self.defs.get(&def_id).and_then(|def_ty| match def_ty { DefTy::Concrete(rty) => Some(rty), - DefTy::Generic(GenericDefTy { rty, .. }) => Some(rty), + DefTy::Generic(GenericDefTy { rty, .. }) => rty.as_ref(), DefTy::Deferred(_) => None, }) } @@ -497,6 +497,20 @@ impl<'tcx> Analyzer<'tcx> { Some(formula_fn) } + fn instantiate_generic_args( + ty: &mut rty::RefinedType, + generic_args: mir_ty::GenericArgsRef<'tcx>, + type_builder: &TypeBuilder<'tcx>, + ) { + ty.instantiate_ty_params( + generic_args + .types() + .map(|ty| type_builder.build(ty)) + .map(rty::RefinedType::unrefined) + .collect(), + ); + } + pub fn def_ty_with_args( &mut self, def_id: DefId, @@ -509,13 +523,7 @@ impl<'tcx> Analyzer<'tcx> { match self.defs.get(&def_id)? { DefTy::Concrete(rty) => { let mut def_ty = rty.clone(); - def_ty.instantiate_ty_params( - generic_args - .types() - .map(|ty| type_builder.build(ty)) - .map(rty::RefinedType::unrefined) - .collect(), - ); + Self::instantiate_generic_args(&mut def_ty, generic_args, &type_builder); return Some(def_ty); } DefTy::Generic(generic) => (generic.local_def_id, Rc::clone(&generic.cache), None), @@ -538,13 +546,7 @@ impl<'tcx> Analyzer<'tcx> { let mut expected = analyzer.expected_ty(); // parameters in annotations are left as params // TODO: remove this after annotation V2 - expected.instantiate_ty_params( - generic_args - .types() - .map(|ty| type_builder.build(ty)) - .map(rty::RefinedType::unrefined) - .collect(), - ); + Self::instantiate_generic_args(&mut expected, generic_args, &type_builder); instantiated_ty_cache .borrow_mut() .insert(generic_args, expected.clone()); diff --git a/src/analyze/crate_.rs b/src/analyze/crate_.rs index bec659e3..7918d89a 100644 --- a/src/analyze/crate_.rs +++ b/src/analyze/crate_.rs @@ -113,12 +113,16 @@ impl<'tcx, 'ctx> Analyzer<'tcx, 'ctx> { } let owner_fn_id = analyzer.owner_fn_id; - let expected = analyzer.expected_ty(); use mir_ty::TypeVisitableExt as _; if sig.has_param() { + let expected = self + .tcx + .is_mir_available(owner_fn_id) + .then(|| analyzer.expected_ty()); self.ctx .register_generic_def(owner_fn_id, local_def_id, expected); } else { + let expected = analyzer.expected_ty(); self.ctx.register_def(owner_fn_id, expected); } } From e4ed770ed19658fdd243b025541306fe2720a0f2 Mon Sep 17 00:00:00 2001 From: coeff-aij <175928954+coeff-aij@users.noreply.github.com> Date: Tue, 9 Jun 2026 22:52:39 +0900 Subject: [PATCH 035/142] change: try to bypass the instantiation of unknown generic args on precompute_callable_param_contracts() --- src/analyze/local_def.rs | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/src/analyze/local_def.rs b/src/analyze/local_def.rs index 7e912627..314f88fd 100644 --- a/src/analyze/local_def.rs +++ b/src/analyze/local_def.rs @@ -397,8 +397,12 @@ impl<'tcx, 'ctx> Analyzer<'tcx, 'ctx> { // `def_ty_with_args` directly. fn precompute_callable_param_contracts(&mut self, sig: &mir_ty::FnSig<'tcx>) { for input_ty in sig.inputs() { - let inst = - mir_ty::EarlyBinder::bind(*input_ty).instantiate(self.tcx, self.generic_args); + use crate::rustc_middle::ty::TypeVisitableExt; + let inst = if input_ty.has_param() && self.generic_args.is_empty() { + *input_ty + } else { + mir_ty::EarlyBinder::bind(*input_ty).instantiate(self.tcx, self.generic_args) + }; let inst = self .tcx .normalize_erasing_regions(mir_ty::TypingEnv::fully_monomorphized(), inst); From 686793628db9f87ae02421eea25bb3f17873a8c2 Mon Sep 17 00:00:00 2001 From: coeff-aij <175928954+coeff-aij@users.noreply.github.com> Date: Thu, 11 Jun 2026 12:51:04 +0900 Subject: [PATCH 036/142] add: register FunctionType for type parameters with Fn/FnMut/FnOnce trait --- src/analyze.rs | 11 ++++- src/analyze/annot_fn.rs | 94 +++++++++++++++++++++++++++++++++++++++++ src/refine/template.rs | 10 +++++ 3 files changed, 114 insertions(+), 1 deletion(-) diff --git a/src/analyze.rs b/src/analyze.rs index 5dbc0d19..74b870b4 100644 --- a/src/analyze.rs +++ b/src/analyze.rs @@ -209,7 +209,7 @@ impl refine::EnumDefProvider for Rc> { pub type Env = refine::Env>>; pub type TypeParamMap = HashMap; -#[derive(Eq, PartialEq, Hash)] +#[derive(Eq, PartialEq, Hash, Debug, Clone)] pub enum TypeParam { GenericType(DefId, u32), AssocType(DefId), @@ -243,6 +243,7 @@ pub struct Analyzer<'tcx> { enum_defs: Rc>, type_params: Rc>, + closure_type_params: Rc>>, } impl<'tcx> crate::refine::TemplateRegistry for Analyzer<'tcx> { @@ -271,6 +272,7 @@ impl<'tcx> Analyzer<'tcx> { let basic_blocks = Default::default(); let enum_defs = Default::default(); let type_params = Default::default(); + let closure_type_params = Default::default(); Self { tcx, defs, @@ -280,6 +282,7 @@ impl<'tcx> Analyzer<'tcx> { def_ids: did_cache::DefIdCache::new(tcx), enum_defs, type_params, + closure_type_params, } } @@ -434,6 +437,10 @@ impl<'tcx> Analyzer<'tcx> { ); } + pub fn get_closure_type(&self, type_param: TypeParam) -> Option { + self.closure_type_params.borrow().get(&type_param).cloned() + } + pub fn concrete_def_ty(&self, def_id: DefId) -> Option<&rty::RefinedType> { self.defs.get(&def_id).and_then(|def_ty| match def_ty { DefTy::Concrete(rty) => Some(rty), @@ -455,6 +462,7 @@ impl<'tcx> Analyzer<'tcx> { self.def_ids(), def_id, self.type_params.clone(), + self.closure_type_params.clone(), self.system.clone(), ); let mut def_ty = match self.defs.get(&def_id)? { @@ -703,6 +711,7 @@ impl<'tcx> Analyzer<'tcx> { def_ids, owner_fn_id, self.type_params.clone(), + self.closure_type_params.clone(), self.system.clone(), ) } diff --git a/src/analyze/annot_fn.rs b/src/analyze/annot_fn.rs index fe88537f..e23161e5 100644 --- a/src/analyze/annot_fn.rs +++ b/src/analyze/annot_fn.rs @@ -159,6 +159,7 @@ impl<'a, 'tcx> AnnotFnTranslator<'a, 'tcx> { def_ids.clone(), local_def_id.to_def_id(), analyzer.type_params.clone(), + analyzer.closure_type_params.clone(), analyzer.system.clone(), ); let mut translator = Self { @@ -188,6 +189,7 @@ impl<'a, 'tcx> AnnotFnTranslator<'a, 'tcx> { self.def_ids.clone(), owner_fn_id, self.analyzer.type_params.clone(), + self.analyzer.closure_type_params.clone(), self.analyzer.system.clone(), ); self @@ -298,12 +300,104 @@ impl<'a, 'tcx> AnnotFnTranslator<'a, 'tcx> { recv_ty = *inner; } let mir_ty::TyKind::Closure(def_id, args) = recv_ty.kind() else { + if let mir_ty::TyKind::Param(ty) = recv_ty.kind() { + tracing::debug!("ParamTy is found: {ty:?}"); + let closure_fun_ty = self.type_param_as_callable_sig(*ty); + tracing::debug!( + "the obtained FunctionType for the closure {ty:?}: {closure_fun_ty:#?}" + ); + if let Some(closure_fun_ty) = closure_fun_ty.clone() { + self.type_builder.register_closure_type_param( + analyze::TypeParam::GenericType(self.local_def_id.to_def_id(), ty.index), + closure_fun_ty, + ); + }; + return closure_fun_ty; + } return None; }; self.analyzer .known_function_ty_with_args(*def_id, self.tcx.mk_args(args.as_closure().parent_args())) } + #[tracing::instrument(skip(self))] + fn closure_trait_args( + &self, + param_ty: mir_ty::ParamTy, + pred: mir_ty::TraitPredicate<'tcx>, + ) -> Option>> { + let trait_ref = pred.trait_ref; + if trait_ref.self_ty() != param_ty.to_ty(self.tcx) { + return None; + } + + let receiver_type = self.type_builder.build(trait_ref.args.type_at(0)); + use mir_ty::ClosureKind::*; + let receiver_type = match self.tcx.fn_trait_kind_from_def_id(trait_ref.def_id)? { + Fn => rty::PointerType::immut_to(receiver_type).into(), + FnMut => rty::PointerType::mut_to(receiver_type).into(), + FnOnce => receiver_type, + }; + + let mir_ty::Tuple(other_params) = trait_ref.args.type_at(1).kind() else { + return None; + }; + + let other_params = other_params.iter().map(|ty| { + let ty = self + .instantiate_generics(ty, self.generic_args) + .unwrap_or(ty); + self.type_builder.build(ty) + }); + let params = std::iter::once(receiver_type) + .chain(other_params) + .map(|ty| rty::RefinedType::unrefined(ty.vacuous())) + .collect(); + tracing::debug!("found the signature for closure trait: {params:#?}"); + Some(params) + } + + #[tracing::instrument(skip(self))] + fn closure_trait_ret( + &self, + param_ty: mir_ty::ParamTy, + pred: mir_ty::ProjectionPredicate<'tcx>, + ) -> Option> { + let projection = pred.projection_term; + if projection.def_id != self.tcx.lang_items().fn_once_output()? + || projection.args.type_at(0) != param_ty.to_ty(self.tcx) + { + return None; + } + + let ret_ty = self.type_builder.build(pred.term.expect_type()).vacuous(); + tracing::debug!(?ret_ty); + Some(rty::RefinedType::unrefined(ret_ty)) + } + + fn type_param_as_callable_sig(&self, param_ty: mir_ty::ParamTy) -> Option { + let param_ty = self + .instantiate_generics(param_ty, self.generic_args) + .unwrap_or(param_ty); + let mut predicates = self + .tcx + .predicates_of(self.local_def_id) + .predicates + .iter() + .map(|(clause, _)| { + self.instantiate_generics(*clause, self.generic_args) + .unwrap_or(*clause) + }); + let params = predicates.clone().find_map(|clause| { + self.closure_trait_args(param_ty, clause.as_trait_clause()?.skip_binder()) + }); + let ret = predicates.find_map(|clause| { + self.closure_trait_ret(param_ty, clause.as_projection_clause()?.skip_binder()) + }); + + Some(rty::FunctionType::new(params?, ret?)) + } + /// Extracts the logical argument terms passed to `closure_precondition`/ /// `closure_postcondition`. The arguments are supplied as a single tuple (e.g. `(x,)` or /// `()`), whose elements are the logical arguments of the closure. diff --git a/src/refine/template.rs b/src/refine/template.rs index 369ff4c5..6297b415 100644 --- a/src/refine/template.rs +++ b/src/refine/template.rs @@ -81,6 +81,7 @@ pub struct TypeBuilder<'tcx> { /// See [`rty::TypeParamIdx`] for more details. param_idx_mapping: HashMap, type_params: Rc>, + closure_type_params: Rc>>, system: Rc>, } @@ -90,6 +91,7 @@ impl<'tcx> TypeBuilder<'tcx> { def_ids: DefIdCache<'tcx>, owner_fn_id: DefId, type_params: Rc>, + closure_type_params: Rc>>, system: Rc>, ) -> Self { let generics = tcx.generics_of(owner_fn_id); @@ -114,6 +116,7 @@ impl<'tcx> TypeBuilder<'tcx> { typing_env, param_idx_mapping, type_params, + closure_type_params, system, } } @@ -153,6 +156,13 @@ impl<'tcx> TypeBuilder<'tcx> { rty::AliasType::new(*index).into() } + pub fn register_closure_type_param(&self, type_param: TypeParam, fun_type: rty::FunctionType) { + tracing::info!(?type_param, ?fun_type, "register_closure_type_param"); + self.closure_type_params + .borrow_mut() + .insert(type_param, fun_type); + } + /// Replaces {closure} types with thrust_models::Closure<{closure}>. /// /// Ideally, we want to have `impl Model for F where F: Fn` instead of this and let From 1c53d16d3be83bec0daaa10902671832bf167786 Mon Sep 17 00:00:00 2001 From: coeff-aij <175928954+coeff-aij@users.noreply.github.com> Date: Thu, 11 Jun 2026 14:27:35 +0900 Subject: [PATCH 037/142] fix: wrong annotations in a test --- tests/ui/pass/traits/simple_loop_call.rs | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/tests/ui/pass/traits/simple_loop_call.rs b/tests/ui/pass/traits/simple_loop_call.rs index de2f0e82..39115fd8 100644 --- a/tests/ui/pass/traits/simple_loop_call.rs +++ b/tests/ui/pass/traits/simple_loop_call.rs @@ -25,11 +25,15 @@ fn target(a: &T, x: i64) -> i64 { v } +#[derive(PartialEq)] struct B(i64); +impl thrust_models::Model for B { + type Ty = B; +} + +#[thrust_macros::context] impl A for B { - #[thrust_macros::requires(Self::p(x))] - #[thrust_macros::ensures(Self::p(result))] fn f(&self, x: i64) -> i64{ x } From 1eb229bf7299d7dc056ddac423162b742cd82f4d Mon Sep 17 00:00:00 2001 From: coeff-aij <175928954+coeff-aij@users.noreply.github.com> Date: Thu, 11 Jun 2026 14:29:17 +0900 Subject: [PATCH 038/142] add: more tests for traits --- tests/ui/pass/traits/simple_loop_2int.rs | 28 +++++++++++++++++ tests/ui/pass/traits/two_loops.rs | 40 ++++++++++++++++++++++++ 2 files changed, 68 insertions(+) create mode 100644 tests/ui/pass/traits/simple_loop_2int.rs create mode 100644 tests/ui/pass/traits/two_loops.rs diff --git a/tests/ui/pass/traits/simple_loop_2int.rs b/tests/ui/pass/traits/simple_loop_2int.rs new file mode 100644 index 00000000..6edb3530 --- /dev/null +++ b/tests/ui/pass/traits/simple_loop_2int.rs @@ -0,0 +1,28 @@ +//@check-pass +//@compile-flags: -C debug-assertions=off +//@rustc-env: THRUST_SOLVER=tests/thrust-pcsat-wrapper THRUST_SOLVER_TIMEOUT_SECS=60 + +#[thrust_macros::context] +trait A { + #[thrust_macros::requires(Self::p(x, x))] + #[thrust_macros::ensures(Self::p(result, result))] + fn f(&self, x: i64) -> i64; + + #[thrust_macros::predicate] + fn p(x: i64, y: i64) -> bool; +} + +#[thrust_macros::requires(T::p(x, x))] +#[thrust_macros::ensures(T::p(result, result))] +fn target(a: &T, x: i64) -> i64 { + let mut v = x; + let mut i = 0; + while i < 3 { + v = a.f(v); + i += 1; + } + + v +} + +fn main() {} diff --git a/tests/ui/pass/traits/two_loops.rs b/tests/ui/pass/traits/two_loops.rs new file mode 100644 index 00000000..198354b1 --- /dev/null +++ b/tests/ui/pass/traits/two_loops.rs @@ -0,0 +1,40 @@ +//@check-pass +//@compile-flags: -C debug-assertions=off +//@rustc-env: THRUST_SOLVER=tests/thrust-pcsat-wrapper THRUST_SOLVER_TIMEOUT_SECS=60 + +#[thrust_macros::context] +trait A { + #[thrust_macros::requires(true)] + #[thrust_macros::ensures(Self::p(*result))] + fn f(&self) -> &Self; + #[thrust_macros::requires(Self::q(*self))] + #[thrust_macros::ensures(Self::q(*result))] + fn g(&self) -> &Self; + + #[thrust_macros::predicate] + fn p(self) -> bool; + #[thrust_macros::predicate] + fn q(self) -> bool; +} + +#[thrust_macros::requires(T::q(*y))] +#[thrust_macros::ensures(T::p(*result.0) && T::q(*result.1))] +fn target<'a, T: A>(x: &'a T, y: &'a T) -> (&'a T, &'a T) { + let mut v = x; + let mut w = y; + let mut i = 0; + while i < 3 { // The loop depends on P + v = v.f(); + i += 1; + } + + let mut j = 0; + while j < 3 { // The loop depends on Q + w = w.g(); + j += 1; + } + + (v, w) +} + +fn main() {} From 52ad0ae0feb92b2de814f2194e4b3f5d331ab41a Mon Sep 17 00:00:00 2001 From: coeff-aij <175928954+coeff-aij@users.noreply.github.com> Date: Thu, 11 Jun 2026 14:29:48 +0900 Subject: [PATCH 039/142] add: test for &mut T (not supported for now) --- tests/ui/pass/traits/simple_loop_self_mut.rs | 32 ++++++++++++++++++++ 1 file changed, 32 insertions(+) create mode 100644 tests/ui/pass/traits/simple_loop_self_mut.rs diff --git a/tests/ui/pass/traits/simple_loop_self_mut.rs b/tests/ui/pass/traits/simple_loop_self_mut.rs new file mode 100644 index 00000000..53a69e22 --- /dev/null +++ b/tests/ui/pass/traits/simple_loop_self_mut.rs @@ -0,0 +1,32 @@ +//@check-pass +//@compile-flags: -C debug-assertions=off +//@rustc-env: THRUST_SOLVER=tests/thrust-pcsat-wrapper THRUST_SOLVER_TIMEOUT_SECS=60 + +#[thrust_macros::context] +trait A { + #[thrust_macros::requires(Self::p(*self, !self, x))] + #[thrust_macros::ensures(Self::p(*self, !self, result))] + fn f(&mut self, x: i64) -> i64; + + #[thrust_macros::predicate] + fn p(self, after: Self, x: i64) -> bool; +} + +// impl thrust_models::Model for A { +// type Ty = A; +// } + +#[thrust_macros::requires(T::p(*a, !a, x))] +#[thrust_macros::ensures(T::p(*a, !a, result))] +fn target(a: &mut T, x: i64) -> i64 { + let mut v = x; + let mut i = 0; + while i < 3 { + v = a.f(v); + i += 1; + } + + v +} + +fn main() {} From eaddc786ae25a56fe952f9bca3e616517bdb2df9 Mon Sep 17 00:00:00 2001 From: coeff-aij <175928954+coeff-aij@users.noreply.github.com> Date: Thu, 11 Jun 2026 15:03:49 +0900 Subject: [PATCH 040/142] fix: error on mutable references with unknown type parameters `&mut T` (ad-hoc) --- src/refine/template.rs | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/src/refine/template.rs b/src/refine/template.rs index 6297b415..d9196384 100644 --- a/src/refine/template.rs +++ b/src/refine/template.rs @@ -282,6 +282,13 @@ impl<'tcx> TypeBuilder<'tcx> { let elem_ty = self.build(*elem_ty); rty::PointerType::immut_to(elem_ty).into() } + mir_ty::TyKind::Ref(_, elem_ty, mir_ty::Mutability::Mut) => { + let elem_ty = self.build(*elem_ty); + if !matches!(elem_ty, rty::Type::Param(_)) { + panic!("unsupported mutable reference type: {elem_ty:?}"); + } + rty::PointerType::mut_to(elem_ty).into() + } mir_ty::TyKind::Tuple(ts) => { // elaboration: all fields are boxed let elems = ts @@ -481,6 +488,13 @@ where let elem_ty = self.build(*elem_ty); rty::PointerType::immut_to(elem_ty).into() } + mir_ty::TyKind::Ref(_, elem_ty, mir_ty::Mutability::Mut) => { + let elem_ty = self.build(*elem_ty); + if !matches!(elem_ty, rty::Type::Param(_)) { + panic!("unsupported mutable reference type: {elem_ty:?}"); + } + rty::PointerType::mut_to(elem_ty).into() + } mir_ty::TyKind::Tuple(ts) => { // elaboration: all fields are boxed let elems = ts From 3fb04522ab45480867ee545630a508e1f304ac49 Mon Sep 17 00:00:00 2001 From: coeff-aij <175928954+coeff-aij@users.noreply.github.com> Date: Fri, 12 Jun 2026 18:04:40 +0900 Subject: [PATCH 041/142] fix: wrong DefId and argument types for type parameter with fn trait --- src/analyze/annot_fn.rs | 19 ++++++------------- 1 file changed, 6 insertions(+), 13 deletions(-) diff --git a/src/analyze/annot_fn.rs b/src/analyze/annot_fn.rs index e23161e5..464022d7 100644 --- a/src/analyze/annot_fn.rs +++ b/src/analyze/annot_fn.rs @@ -308,7 +308,7 @@ impl<'a, 'tcx> AnnotFnTranslator<'a, 'tcx> { ); if let Some(closure_fun_ty) = closure_fun_ty.clone() { self.type_builder.register_closure_type_param( - analyze::TypeParam::GenericType(self.local_def_id.to_def_id(), ty.index), + analyze::TypeParam::GenericType(self.type_builder.owner_fn_id, ty.index), closure_fun_ty, ); }; @@ -330,8 +330,10 @@ impl<'a, 'tcx> AnnotFnTranslator<'a, 'tcx> { if trait_ref.self_ty() != param_ty.to_ty(self.tcx) { return None; } + tracing::debug!(?trait_ref.args); let receiver_type = self.type_builder.build(trait_ref.args.type_at(0)); + use mir_ty::ClosureKind::*; let receiver_type = match self.tcx.fn_trait_kind_from_def_id(trait_ref.def_id)? { Fn => rty::PointerType::immut_to(receiver_type).into(), @@ -339,18 +341,9 @@ impl<'a, 'tcx> AnnotFnTranslator<'a, 'tcx> { FnOnce => receiver_type, }; - let mir_ty::Tuple(other_params) = trait_ref.args.type_at(1).kind() else { - return None; - }; - - let other_params = other_params.iter().map(|ty| { - let ty = self - .instantiate_generics(ty, self.generic_args) - .unwrap_or(ty); - self.type_builder.build(ty) - }); - let params = std::iter::once(receiver_type) - .chain(other_params) + let other_params = self.type_builder.build(trait_ref.args.type_at(1)); + let params = [receiver_type, other_params] + .into_iter() .map(|ty| rty::RefinedType::unrefined(ty.vacuous())) .collect(); tracing::debug!("found the signature for closure trait: {params:#?}"); From 11b2ffd9a134f3bb8fcb9ce79d5760d3be12b1c7 Mon Sep 17 00:00:00 2001 From: coeff-aij <175928954+coeff-aij@users.noreply.github.com> Date: Fri, 12 Jun 2026 18:06:45 +0900 Subject: [PATCH 042/142] add: resolve type parameter with fn trait as FunctionType --- src/analyze/basic_block.rs | 62 +++++++++++++------- src/analyze/basic_block/visitor/rust_call.rs | 2 +- 2 files changed, 43 insertions(+), 21 deletions(-) diff --git a/src/analyze/basic_block.rs b/src/analyze/basic_block.rs index d7447024..0a0835f9 100644 --- a/src/analyze/basic_block.rs +++ b/src/analyze/basic_block.rs @@ -9,7 +9,7 @@ use rustc_middle::mir::{ use rustc_middle::ty::{self as mir_ty, TyCtxt}; use rustc_span::def_id::{DefId, LocalDefId}; -use crate::analyze; +use crate::analyze::{self, TypeParam}; use crate::chc; use crate::pretty::PrettyDisplayExt as _; use crate::refine::{ @@ -131,6 +131,11 @@ impl PrecondCapture { } } +enum ResolvedCallable<'tcx> { + Closure(DefId, mir_ty::GenericArgsRef<'tcx>), + Generic(TypeParam), +} + pub struct Analyzer<'tcx, 'ctx> { ctx: &'ctx mut analyze::Analyzer<'tcx>, tcx: TyCtxt<'tcx>, @@ -603,7 +608,7 @@ impl<'tcx, 'ctx> Analyzer<'tcx, 'ctx> { _ty, ) => { let func_ty = match operand.const_fn_def() { - Some((def_id, args)) => self.fn_def_ty(def_id, args), + Some((def_id, args)) => self.callable_ty(def_id, args), _ => unimplemented!(), }; PlaceType::with_ty_and_term(func_ty.vacuous(), chc::Term::null()) @@ -830,40 +835,47 @@ impl<'tcx, 'ctx> Analyzer<'tcx, 'ctx> { }); } - fn resolve_fn_def( + fn resolve_callable( &self, def_id: DefId, args: mir_ty::GenericArgsRef<'tcx>, - ) -> (DefId, mir_ty::GenericArgsRef<'tcx>) { + ) -> ResolvedCallable<'tcx> { if self.ctx.is_fn_trait_method(def_id) { // When calling a closure via `Fn`/`FnMut`/`FnOnce` trait, // we simply replace the def_id with the closure's function def_id. // This skips shims, and makes self arguments mismatch. visitor::RustCallVisitor // adjusts the arguments accordingly. - let mir_ty::TyKind::Closure(closure_def_id, closure_args) = args.type_at(0).kind() - else { - panic!("expected closure arg for fn trait"); - }; - tracing::debug!(?closure_def_id, "closure instance"); - // closure_args contains [parent_generics..., upvars, return_ty, fn_sig_binder, ...]. - // Only the parent generics are meaningful to def_ty_with_args; the rest are internal - // closure encoding that type_builder.build() cannot handle. - let parent_count = self.tcx.generics_of(*closure_def_id).parent_count; - let parent_args = self.tcx.mk_args(&closure_args[..parent_count]); - (*closure_def_id, parent_args) + match args.type_at(0).kind() { + mir_ty::TyKind::Closure(closure_def_id, closure_args) => { + tracing::debug!(?closure_def_id, "closure instance"); + // closure_args contains [parent_generics..., upvars, return_ty, fn_sig_binder, ...]. + // Only the parent generics are meaningful to def_ty_with_args; the rest are internal + // closure encoding that type_builder.build() cannot handle. + let parent_count = self.tcx.generics_of(*closure_def_id).parent_count; + let parent_args = self.tcx.mk_args(&closure_args[..parent_count]); + ResolvedCallable::Closure(*closure_def_id, parent_args) + } + mir_ty::TyKind::Param(ty) => ResolvedCallable::Generic(TypeParam::GenericType( + self.type_builder.owner_fn_id, + ty.index, + )), + kind => { + panic!("expected closure arg for fn trait, got: {kind:?}"); + } + } } else { let typing_env = self.body.typing_env(self.tcx); let instance = mir_ty::Instance::try_resolve(self.tcx, typing_env, def_id, args).unwrap(); if let Some(instance) = instance { - (instance.def_id(), instance.args) + ResolvedCallable::Closure(instance.def_id(), instance.args) } else { - (def_id, args) + ResolvedCallable::Closure(def_id, args) } } } - fn fn_def_ty( + fn callable_ty( &mut self, def_id: DefId, args: mir_ty::GenericArgsRef<'tcx>, @@ -873,7 +885,17 @@ impl<'tcx, 'ctx> Analyzer<'tcx, 'ctx> { return def_ty.ty; } - let (resolved_def_id, resolved_args) = self.resolve_fn_def(def_id, args); + let (resolved_def_id, resolved_args) = match self.resolve_callable(def_id, args) { + ResolvedCallable::Closure(def_id, args) => (def_id, args), + ResolvedCallable::Generic(type_param) => { + tracing::debug!(?type_param, ?self.ctx.closure_type_params); + return self + .ctx + .get_closure_type(type_param) + .expect("unknown closure type") + .into(); + } + }; if resolved_def_id == def_id { panic!( "unknown def (and not resolved): {:?}, args: {:?}", @@ -899,7 +921,7 @@ impl<'tcx, 'ctx> Analyzer<'tcx, 'ctx> { { // TODO: handle const_fn_def on Env side let func_ty = if let Some((def_id, args)) = func.const_fn_def() { - self.fn_def_ty(def_id, args).vacuous() + self.callable_ty(def_id, args).vacuous() } else { self.operand_type(func.clone()).ty }; diff --git a/src/analyze/basic_block/visitor/rust_call.rs b/src/analyze/basic_block/visitor/rust_call.rs index 53100100..53191be7 100644 --- a/src/analyze/basic_block/visitor/rust_call.rs +++ b/src/analyze/basic_block/visitor/rust_call.rs @@ -59,7 +59,7 @@ impl<'a, 'tcx, 'ctx> mir::visit::MutVisitor<'tcx> for RustCallVisitor<'a, 'tcx, // RustCallVisitor expects all generic args to be already instantiated let mir_ty::TyKind::Closure(resolved_def_id, _) = generic_args.type_at(0).kind() else { - panic!("expected closure arg for fn trait"); + return; }; let fn_sig = self.analyzer.ctx().fn_sig(*resolved_def_id); if !matches!(fn_sig.abi, rustc_abi::ExternAbi::RustCall) { From d40d441ea0e4132def9d54ca04aac6fbd3ec79e1 Mon Sep 17 00:00:00 2001 From: coeff-aij <175928954+coeff-aij@users.noreply.github.com> Date: Sat, 13 Jun 2026 19:03:05 +0900 Subject: [PATCH 043/142] fix: double instantiation of argument types in precompute_callable_param_contracts() --- src/analyze/local_def.rs | 9 ++------- 1 file changed, 2 insertions(+), 7 deletions(-) diff --git a/src/analyze/local_def.rs b/src/analyze/local_def.rs index 314f88fd..a5d02123 100644 --- a/src/analyze/local_def.rs +++ b/src/analyze/local_def.rs @@ -397,15 +397,9 @@ impl<'tcx, 'ctx> Analyzer<'tcx, 'ctx> { // `def_ty_with_args` directly. fn precompute_callable_param_contracts(&mut self, sig: &mir_ty::FnSig<'tcx>) { for input_ty in sig.inputs() { - use crate::rustc_middle::ty::TypeVisitableExt; - let inst = if input_ty.has_param() && self.generic_args.is_empty() { - *input_ty - } else { - mir_ty::EarlyBinder::bind(*input_ty).instantiate(self.tcx, self.generic_args) - }; let inst = self .tcx - .normalize_erasing_regions(mir_ty::TypingEnv::fully_monomorphized(), inst); + .normalize_erasing_regions(mir_ty::TypingEnv::fully_monomorphized(), *input_ty); let (fn_def_id, fn_args) = match inst.kind() { mir_ty::TyKind::Closure(def_id, args) => { (*def_id, self.tcx.mk_args(args.as_closure().parent_args())) @@ -1237,6 +1231,7 @@ impl<'tcx, 'ctx> Analyzer<'tcx, 'ctx> { pub fn owner_fn_id(&mut self, owner_fn_id: DefId) -> &mut Self { self.owner_fn_id = owner_fn_id; + self.type_builder = self.ctx.type_builder(self.ctx.def_ids(), owner_fn_id); self } From 55d02cabda7e35fd48710a7363c6ea1533ff4906 Mon Sep 17 00:00:00 2001 From: coeff-aij <175928954+coeff-aij@users.noreply.github.com> Date: Sat, 13 Jun 2026 19:04:28 +0900 Subject: [PATCH 044/142] change: distinguish ForallPred with type parameters instead of argument types --- src/analyze/annot_fn.rs | 16 +++++----------- src/chc.rs | 9 ++++++--- src/chc/format_context.rs | 2 +- src/chc/smtlib2.rs | 6 +++++- src/chc/unbox.rs | 7 +++++-- 5 files changed, 22 insertions(+), 18 deletions(-) diff --git a/src/analyze/annot_fn.rs b/src/analyze/annot_fn.rs index 464022d7..1aeb1d9c 100644 --- a/src/analyze/annot_fn.rs +++ b/src/analyze/annot_fn.rs @@ -761,19 +761,13 @@ impl<'a, 'tcx> AnnotFnTranslator<'a, 'tcx> { def_id }; - let typeck_results = self.tcx.typeck(self.local_def_id); let pred = if is_unresolved_args { - let sig: Vec = args - .iter() - .map(|e| { - let ty = typeck_results.expr_ty(e); - let ty = self - .instantiate_generics(ty, generic_args) - .unwrap_or(ty); - self.type_builder.build(ty).to_sort() - }) + tracing::debug!(?self.local_def_id, ?generic_args, ?self.type_builder.owner_fn_id); + let type_params = generic_args + .types() + .map(|ty| self.type_builder.build(ty).to_sort()) .collect(); - let pred = refine::forall_pred(self.tcx, pred_def_id, sig); + let pred = refine::forall_pred(self.tcx, pred_def_id, type_params); self.analyzer .system .borrow_mut() diff --git a/src/chc.rs b/src/chc.rs index 53662747..9861319a 100644 --- a/src/chc.rs +++ b/src/chc.rs @@ -1050,7 +1050,7 @@ impl UserDefinedPred { #[derive(Debug, Clone, PartialEq, Eq, Hash)] pub struct ForallPred { inner: String, - args: Vec, + type_parameters: Vec, } impl std::fmt::Display for ForallPred { @@ -1066,7 +1066,7 @@ where { fn pretty(self, allocator: &'a D) -> pretty::DocBuilder<'a, D, termcolor::ColorSpec> { let args = allocator.intersperse( - self.args.iter().map(|a| a.pretty(allocator)), + self.type_parameters.iter().map(|a| a.pretty(allocator)), allocator.text(", "), ); allocator @@ -1083,7 +1083,10 @@ where impl ForallPred { pub fn new(inner: String, args: Vec) -> Self { - Self { inner, args } + Self { + inner, + type_parameters: args, + } } } diff --git a/src/chc/format_context.rs b/src/chc/format_context.rs index ad54df74..ce583c2b 100644 --- a/src/chc/format_context.rs +++ b/src/chc/format_context.rs @@ -349,7 +349,7 @@ impl FormatContext { } pub fn forall_pred(&self, p: &chc::ForallPred) -> impl std::fmt::Display { - let ss = SortSymbols::new(&p.args); + let ss = SortSymbols::new(&p.type_parameters); format!("{}{}", p.inner, ss) } diff --git a/src/chc/smtlib2.rs b/src/chc/smtlib2.rs index 929ff547..fdc2abe6 100644 --- a/src/chc/smtlib2.rs +++ b/src/chc/smtlib2.rs @@ -600,7 +600,11 @@ pub struct ForallPredDef<'ctx, 'a> { impl<'ctx, 'a> std::fmt::Display for ForallPredDef<'ctx, 'a> { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - let params = self.pred.args.iter().map(|sort| self.ctx.fmt_sort(sort)); + let params = self + .pred + .type_parameters + .iter() + .map(|sort| self.ctx.fmt_sort(sort)); let params = List::closed(params); write!( f, diff --git a/src/chc/unbox.rs b/src/chc/unbox.rs index 3b8bd6a8..8817e0a9 100644 --- a/src/chc/unbox.rs +++ b/src/chc/unbox.rs @@ -165,8 +165,11 @@ fn unbox_user_defined_pred_def(user_defined_pred_def: UserDefinedPredDef) -> Use } fn unbox_forall_pred_var_def(pred: ForallPred) -> ForallPred { - let args = pred.args.into_iter().map(unbox_sort).collect(); - ForallPred { args, ..pred } + let args = pred.type_parameters.into_iter().map(unbox_sort).collect(); + ForallPred { + type_parameters: args, + ..pred + } } /// Remove all `Box` sorts and `Box`/`BoxCurrent` terms from the system. From 482b186c73b522fd095cada70590c67bc0539907 Mon Sep 17 00:00:00 2001 From: coeff-aij <175928954+coeff-aij@users.noreply.github.com> Date: Sat, 13 Jun 2026 19:40:39 +0900 Subject: [PATCH 045/142] add: introduce ForallPreds corresponding to pre-/post-condition of type parameters with fn traits --- src/analyze/annot_fn.rs | 40 +++++++++++++++++++++++++++++++++++----- 1 file changed, 35 insertions(+), 5 deletions(-) diff --git a/src/analyze/annot_fn.rs b/src/analyze/annot_fn.rs index 1aeb1d9c..07966a40 100644 --- a/src/analyze/annot_fn.rs +++ b/src/analyze/annot_fn.rs @@ -368,6 +368,16 @@ impl<'a, 'tcx> AnnotFnTranslator<'a, 'tcx> { Some(rty::RefinedType::unrefined(ret_ty)) } + fn register_forall_pred(&self, type_params: Vec) -> chc::ForallPred { + let predicate = + refine::forall_pred(self.tcx, self.local_def_id.to_def_id(), type_params.clone()); + self.analyzer + .system + .borrow_mut() + .register_forall_pred(predicate.clone()); + predicate + } + fn type_param_as_callable_sig(&self, param_ty: mir_ty::ParamTy) -> Option { let param_ty = self .instantiate_generics(param_ty, self.generic_args) @@ -381,14 +391,34 @@ impl<'a, 'tcx> AnnotFnTranslator<'a, 'tcx> { self.instantiate_generics(*clause, self.generic_args) .unwrap_or(*clause) }); - let params = predicates.clone().find_map(|clause| { + + let mut params = predicates.clone().find_map(|clause| { self.closure_trait_args(param_ty, clause.as_trait_clause()?.skip_binder()) - }); - let ret = predicates.find_map(|clause| { + })?; + let mut ret = predicates.find_map(|clause| { self.closure_trait_ret(param_ty, clause.as_projection_clause()?.skip_binder()) - }); + })?; + + let receiver = rty::FunctionParamIdx::from_usize(0); + let arg = rty::FunctionParamIdx::from_usize(1); + + let free = |idx| chc::Term::var(rty::RefinedTypeVar::Free(idx)); + let value = chc::Term::var(rty::RefinedTypeVar::Value); + + let type_params = vec![self.type_builder.build(param_ty.to_ty(self.tcx)).to_sort()]; + + let pre_pred = self.register_forall_pred(type_params.clone()); + let post_pred = self.register_forall_pred(type_params); + + params[receiver].extend_refinement( + chc::Atom::new(pre_pred.into(), vec![value.clone(), free(arg)]).into(), + ); + + ret.extend_refinement( + chc::Atom::new(post_pred.into(), vec![free(receiver), free(arg), value]).into(), + ); - Some(rty::FunctionType::new(params?, ret?)) + Some(rty::FunctionType::new(params, ret)) } /// Extracts the logical argument terms passed to `closure_precondition`/ From acb05790fded47c5146f9447d80640bbe15d5985 Mon Sep 17 00:00:00 2001 From: coeff-aij <175928954+coeff-aij@users.noreply.github.com> Date: Sat, 13 Jun 2026 20:03:48 +0900 Subject: [PATCH 046/142] change: use forall sort instead of i32 type to represent unknown type parameters without trait bounds --- src/analyze/crate_.rs | 18 +++++++----------- 1 file changed, 7 insertions(+), 11 deletions(-) diff --git a/src/analyze/crate_.rs b/src/analyze/crate_.rs index 7918d89a..f2b19802 100644 --- a/src/analyze/crate_.rs +++ b/src/analyze/crate_.rs @@ -183,18 +183,14 @@ impl<'tcx, 'ctx> Analyzer<'tcx, 'ctx> { let param = generics.param_at(idx, self.tcx); let arg = match param.kind { mir_ty::GenericParamDefKind::Type { .. } => { - if constrained_params.contains(¶m.index) { - let new_param = - mir_ty::Ty::new_param(self.tcx, param.index, param.name).into(); - tracing::debug!( - "replace the cosnstrained param {:#?} with the new param {:#?}.", - param, - new_param - ); + let new_param = + mir_ty::Ty::new_param(self.tcx, param.index, param.name).into(); + tracing::debug!( + "replace the cosnstrained param {:#?} with the new param {:#?}.", + param, new_param - } else { - self.tcx.types.i32.into() - } + ); + new_param } mir_ty::GenericParamDefKind::Const { .. } => { unimplemented!() From 8121a82245a0c866d20396686351e149842a1389 Mon Sep 17 00:00:00 2001 From: coeff-aij <175928954+coeff-aij@users.noreply.github.com> Date: Sat, 13 Jun 2026 20:23:39 +0900 Subject: [PATCH 047/142] revert: comment out for some extern_spec in std.rs --- std.rs | 218 ++++++++++++++++++++++++++++----------------------------- 1 file changed, 109 insertions(+), 109 deletions(-) diff --git a/std.rs b/std.rs index 56aa2d06..35d1a4c2 100644 --- a/std.rs +++ b/std.rs @@ -363,51 +363,51 @@ mod thrust_models { // std::mem::replace(dest, src) // } -// #[thrust::extern_spec_fn] -// #[thrust_macros::requires(true)] -// #[thrust_macros::ensures(result == (x == y))] -// fn _extern_spec_option_partialeq_eq(x: &Option, y: &Option) -> bool -// where T: thrust_models::Model + PartialEq, T::Ty: PartialEq -// { -// as PartialEq>::eq(x, y) -// } +#[thrust::extern_spec_fn] +#[thrust_macros::requires(true)] +#[thrust_macros::ensures(result == (x == y))] +fn _extern_spec_option_partialeq_eq(x: &Option, y: &Option) -> bool + where T: thrust_models::Model + PartialEq, T::Ty: PartialEq +{ + as PartialEq>::eq(x, y) +} -// #[thrust::extern_spec_fn] -// #[thrust_macros::requires(opt != None)] -// #[thrust_macros::ensures(Some(result) == opt)] -// fn _extern_spec_option_unwrap(opt: Option) -> T where T: thrust_models::Model, T::Ty: PartialEq { -// Option::unwrap(opt) -// } +#[thrust::extern_spec_fn] +#[thrust_macros::requires(opt != None)] +#[thrust_macros::ensures(Some(result) == opt)] +fn _extern_spec_option_unwrap(opt: Option) -> T where T: thrust_models::Model, T::Ty: PartialEq { + Option::unwrap(opt) +} -// #[thrust::extern_spec_fn] -// #[thrust_macros::requires(true)] -// #[thrust_macros::ensures( -// (*opt == None && result == true) -// || (*opt != None && result == false) -// )] -// fn _extern_spec_option_is_none(opt: &Option) -> bool where T: thrust_models::Model, T::Ty: PartialEq { -// Option::is_none(opt) -// } +#[thrust::extern_spec_fn] +#[thrust_macros::requires(true)] +#[thrust_macros::ensures( + (*opt == None && result == true) + || (*opt != None && result == false) +)] +fn _extern_spec_option_is_none(opt: &Option) -> bool where T: thrust_models::Model, T::Ty: PartialEq { + Option::is_none(opt) +} -// #[thrust::extern_spec_fn] -// #[thrust_macros::requires(true)] -// #[thrust_macros::ensures( -// (*opt == None && result == false) -// || (*opt != None && result == true) -// )] -// fn _extern_spec_option_is_some(opt: &Option) -> bool where T: thrust_models::Model, T::Ty: PartialEq { -// Option::is_some(opt) -// } +#[thrust::extern_spec_fn] +#[thrust_macros::requires(true)] +#[thrust_macros::ensures( + (*opt == None && result == false) + || (*opt != None && result == true) +)] +fn _extern_spec_option_is_some(opt: &Option) -> bool where T: thrust_models::Model, T::Ty: PartialEq { + Option::is_some(opt) +} -// #[thrust::extern_spec_fn] -// #[thrust_macros::requires(true)] -// #[thrust_macros::ensures( -// (opt != None && Some(result) == opt) -// || (opt == None && result == default) -// )] -// fn _extern_spec_option_unwrap_or(opt: Option, default: T) -> T where T: thrust_models::Model, T::Ty: PartialEq { -// Option::unwrap_or(opt, default) -// } +#[thrust::extern_spec_fn] +#[thrust_macros::requires(true)] +#[thrust_macros::ensures( + (opt != None && Some(result) == opt) + || (opt == None && result == default) +)] +fn _extern_spec_option_unwrap_or(opt: Option, default: T) -> T where T: thrust_models::Model, T::Ty: PartialEq { + Option::unwrap_or(opt, default) +} // #[thrust::extern_spec_fn] // #[thrust_macros::requires( @@ -500,15 +500,15 @@ mod thrust_models { // Option::as_mut(opt) // } -// #[thrust::extern_spec_fn] -// #[thrust_macros::requires(true)] -// #[thrust_macros::ensures(result == (x == y))] -// fn _extern_spec_result_partialeq_eq(x: &Result, y: &Result) -> bool -// where T: thrust_models::Model + PartialEq, T::Ty: PartialEq, -// E: thrust_models::Model + PartialEq, E::Ty: PartialEq, -// { -// as PartialEq>::eq(x, y) -// } +#[thrust::extern_spec_fn] +#[thrust_macros::requires(true)] +#[thrust_macros::ensures(result == (x == y))] +fn _extern_spec_result_partialeq_eq(x: &Result, y: &Result) -> bool + where T: thrust_models::Model + PartialEq, T::Ty: PartialEq, + E: thrust_models::Model + PartialEq, E::Ty: PartialEq, +{ + as PartialEq>::eq(x, y) +} // #[thrust::extern_spec_fn] // #[thrust_macros::requires(thrust_models::exists(|x| res == Ok(x)))] @@ -582,43 +582,43 @@ mod thrust_models { // Result::is_err(res) // } -// #[thrust::extern_spec_fn] -// #[thrust_macros::requires(true)] // TODO: require x != i32::MIN -// #[thrust_macros::ensures(result >= 0 && (result == x || result == -x))] -// fn _extern_spec_i32_abs(x: i32) -> i32 { -// i32::abs(x) -// } +#[thrust::extern_spec_fn] +#[thrust_macros::requires(true)] // TODO: require x != i32::MIN +#[thrust_macros::ensures(result >= 0 && (result == x || result == -x))] +fn _extern_spec_i32_abs(x: i32) -> i32 { + i32::abs(x) +} -// #[thrust::extern_spec_fn] -// #[thrust_macros::requires(true)] -// #[thrust_macros::ensures( -// (x >= y && result == (x - y)) -// || (x < y && result == (y - x)) -// )] -// fn _extern_spec_i32_abs_diff(x: i32, y: i32) -> u32 { -// i32::abs_diff(x, y) -// } +#[thrust::extern_spec_fn] +#[thrust_macros::requires(true)] +#[thrust_macros::ensures( + (x >= y && result == (x - y)) + || (x < y && result == (y - x)) +)] +fn _extern_spec_i32_abs_diff(x: i32, y: i32) -> u32 { + i32::abs_diff(x, y) +} -// #[thrust::extern_spec_fn] -// #[thrust_macros::requires(true)] -// #[thrust_macros::ensures((x == 0 && result == 0) || (x > 0 && result == 1) || (x < 0 && result == -1))] -// fn _extern_spec_i32_signum(x: i32) -> i32 { -// i32::signum(x) -// } +#[thrust::extern_spec_fn] +#[thrust_macros::requires(true)] +#[thrust_macros::ensures((x == 0 && result == 0) || (x > 0 && result == 1) || (x < 0 && result == -1))] +fn _extern_spec_i32_signum(x: i32) -> i32 { + i32::signum(x) +} -// #[thrust::extern_spec_fn] -// #[thrust_macros::requires(true)] -// #[thrust_macros::ensures((x < 0 && result == false) || (x >= 0 && result == true))] -// fn _extern_spec_i32_is_positive(x: i32) -> bool { -// i32::is_positive(x) -// } +#[thrust::extern_spec_fn] +#[thrust_macros::requires(true)] +#[thrust_macros::ensures((x < 0 && result == false) || (x >= 0 && result == true))] +fn _extern_spec_i32_is_positive(x: i32) -> bool { + i32::is_positive(x) +} -// #[thrust::extern_spec_fn] -// #[thrust_macros::requires(true)] -// #[thrust_macros::ensures((x <= 0 && result == true) || (x > 0 && result == false))] -// fn _extern_spec_i32_is_negative(x: i32) -> bool { -// i32::is_negative(x) -// } +#[thrust::extern_spec_fn] +#[thrust_macros::requires(true)] +#[thrust_macros::ensures((x <= 0 && result == true) || (x > 0 && result == false))] +fn _extern_spec_i32_is_negative(x: i32) -> bool { + i32::is_negative(x) +} // #[thrust::extern_spec_fn] // #[thrust_macros::requires(true)] @@ -711,32 +711,32 @@ mod thrust_models { // Vec::truncate(vec, len) // } -// // TODO: The following specs of some trait methods are too restrictive; we should allow for a -// // per-impl spec once we can describe the spec of blanket impls. +// TODO: The following specs of some trait methods are too restrictive; we should allow for a +// per-impl spec once we can describe the spec of blanket impls. -// #[thrust::extern_spec_fn] -// #[thrust_macros::requires(true)] -// #[thrust_macros::ensures(result == (*x == *y))] -// fn _extern_spec_partialeq_eq(x: &T, y: &T) -> bool -// where T: thrust_models::Model + PartialEq, T::Ty: PartialEq -// { -// PartialEq::eq(x, y) -// } +#[thrust::extern_spec_fn] +#[thrust_macros::requires(true)] +#[thrust_macros::ensures(result == (*x == *y))] +fn _extern_spec_partialeq_eq(x: &T, y: &T) -> bool + where T: thrust_models::Model + PartialEq, T::Ty: PartialEq +{ + PartialEq::eq(x, y) +} -// #[thrust::extern_spec_fn] -// #[thrust_macros::requires(true)] -// #[thrust_macros::ensures(result == (*x < *y))] -// fn _extern_spec_partialord_lt(x: &T, y: &T) -> bool -// where T: thrust_models::Model + PartialOrd, T::Ty: PartialOrd -// { -// PartialOrd::lt(x, y) -// } +#[thrust::extern_spec_fn] +#[thrust_macros::requires(true)] +#[thrust_macros::ensures(result == (*x < *y))] +fn _extern_spec_partialord_lt(x: &T, y: &T) -> bool + where T: thrust_models::Model + PartialOrd, T::Ty: PartialOrd +{ + PartialOrd::lt(x, y) +} -// #[thrust::extern_spec_fn] -// #[thrust_macros::requires(true)] -// #[thrust_macros::ensures(result == (*x > *y))] -// fn _extern_spec_partialord_gt(x: &T, y: &T) -> bool -// where T: thrust_models::Model + PartialOrd, T::Ty: PartialOrd -// { -// PartialOrd::gt(x, y) -// } +#[thrust::extern_spec_fn] +#[thrust_macros::requires(true)] +#[thrust_macros::ensures(result == (*x > *y))] +fn _extern_spec_partialord_gt(x: &T, y: &T) -> bool + where T: thrust_models::Model + PartialOrd, T::Ty: PartialOrd +{ + PartialOrd::gt(x, y) +} From f55687e553c992954055c8dceef554f69ebe1669 Mon Sep 17 00:00:00 2001 From: coeff-aij <175928954+coeff-aij@users.noreply.github.com> Date: Sun, 14 Jun 2026 03:03:19 +0900 Subject: [PATCH 048/142] add: distinguish args of projection(e.g. ::Item and ::Item) --- src/analyze.rs | 12 +++--- src/analyze/basic_block.rs | 2 +- src/refine/template.rs | 22 +++++++---- src/rty.rs | 76 ++++++++++++++++++++++++++++++++++---- src/rty/subtyping.rs | 2 + 5 files changed, 92 insertions(+), 22 deletions(-) diff --git a/src/analyze.rs b/src/analyze.rs index 74b870b4..94b43a60 100644 --- a/src/analyze.rs +++ b/src/analyze.rs @@ -207,12 +207,12 @@ impl refine::EnumDefProvider for Rc> { } pub type Env = refine::Env>>; -pub type TypeParamMap = HashMap; +pub type TypeParamMap<'tcx> = HashMap, ForallSortIdx>; #[derive(Eq, PartialEq, Hash, Debug, Clone)] -pub enum TypeParam { +pub enum TypeParam<'tcx> { GenericType(DefId, u32), - AssocType(DefId), + AssocType(DefId, mir_ty::GenericArgsRef<'tcx>), } #[derive(Debug, Clone)] @@ -242,8 +242,8 @@ pub struct Analyzer<'tcx> { enum_defs: Rc>, - type_params: Rc>, - closure_type_params: Rc>>, + type_params: Rc>>, + closure_type_params: Rc, rty::FunctionType>>>, } impl<'tcx> crate::refine::TemplateRegistry for Analyzer<'tcx> { @@ -437,7 +437,7 @@ impl<'tcx> Analyzer<'tcx> { ); } - pub fn get_closure_type(&self, type_param: TypeParam) -> Option { + pub fn get_closure_type(&self, type_param: TypeParam<'tcx>) -> Option { self.closure_type_params.borrow().get(&type_param).cloned() } diff --git a/src/analyze/basic_block.rs b/src/analyze/basic_block.rs index 0a0835f9..28b56fa4 100644 --- a/src/analyze/basic_block.rs +++ b/src/analyze/basic_block.rs @@ -133,7 +133,7 @@ impl PrecondCapture { enum ResolvedCallable<'tcx> { Closure(DefId, mir_ty::GenericArgsRef<'tcx>), - Generic(TypeParam), + Generic(TypeParam<'tcx>), } pub struct Analyzer<'tcx, 'ctx> { diff --git a/src/refine/template.rs b/src/refine/template.rs index d9196384..9deff0b4 100644 --- a/src/refine/template.rs +++ b/src/refine/template.rs @@ -80,8 +80,8 @@ pub struct TypeBuilder<'tcx> { /// mapped when we translate a [`mir_ty::ParamTy`] to [`rty::ParamType`]. /// See [`rty::TypeParamIdx`] for more details. param_idx_mapping: HashMap, - type_params: Rc>, - closure_type_params: Rc>>, + type_params: Rc>>, + closure_type_params: Rc, rty::FunctionType>>>, system: Rc>, } @@ -90,8 +90,8 @@ impl<'tcx> TypeBuilder<'tcx> { tcx: mir_ty::TyCtxt<'tcx>, def_ids: DefIdCache<'tcx>, owner_fn_id: DefId, - type_params: Rc>, - closure_type_params: Rc>>, + type_params: Rc>>, + closure_type_params: Rc, rty::FunctionType>>>, system: Rc>, ) -> Self { let generics = tcx.generics_of(owner_fn_id); @@ -143,20 +143,26 @@ impl<'tcx> TypeBuilder<'tcx> { rty::ParamType::new(param_local_idx, *forall_sort_idx).into() } - fn translate_alias_type(&self, ty: &mir_ty::AliasTy) -> rty::Type { + fn translate_alias_type(&self, ty: &mir_ty::AliasTy<'tcx>) -> rty::Type { let mut type_params = self.type_params.borrow_mut(); let index = type_params - .entry(TypeParam::AssocType(ty.def_id)) + .entry(TypeParam::AssocType(ty.def_id, ty.args)) .or_insert_with(|| { let idx = self.system.borrow_mut().new_forall_sort(); tracing::debug!("issue the new ForallSortIdx {} for AliasTy {:?}.", idx, ty,); idx }); - rty::AliasType::new(*index).into() + let args: Vec> = ty.args.types().map(|t| self.build(t)).collect(); + + rty::AliasType::new(*index, args).into() } - pub fn register_closure_type_param(&self, type_param: TypeParam, fun_type: rty::FunctionType) { + pub fn register_closure_type_param( + &self, + type_param: TypeParam<'tcx>, + fun_type: rty::FunctionType, + ) { tracing::info!(?type_param, ?fun_type, "register_closure_type_param"); self.closure_type_params .borrow_mut() diff --git a/src/rty.rs b/src/rty.rs index da781836..13e1e060 100644 --- a/src/rty.rs +++ b/src/rty.rs @@ -763,28 +763,56 @@ impl ParamType { } } +/// A projection type representing an unresolved associated type. +/// +/// This preserves the structural identity of projections like `::Item` +/// or ` as Iterator>::Item`, keeping them distinct even before normalization. +/// +/// The `args` field stores the generic arguments (Self type + other args), which can +/// recursively contain other types including params, ADTs, and other projections. +/// For example, ` as Iterator>::Item` would have `args = [Map]`. #[derive(Debug, Clone)] pub struct AliasType { forall_sort_idx: ForallSortIdx, + args: Vec>, } impl<'a, D> Pretty<'a, D, termcolor::ColorSpec> for &AliasType where D: pretty::DocAllocator<'a, termcolor::ColorSpec>, + D::Doc: Clone, { fn pretty(self, allocator: &'a D) -> pretty::DocBuilder<'a, D, termcolor::ColorSpec> { - self.forall_sort_idx.pretty(allocator) + let sort = self.forall_sort_idx.pretty(allocator); + if self.args.is_empty() { + sort + } else { + let args = allocator.intersperse( + self.args.iter().map(|ty| ty.pretty(allocator)), + allocator.text(",").append(allocator.line()), + ); + sort.append(allocator.line()) + .append(args.nest(2).angles()) + .group() + } } } impl AliasType { - pub fn new(forall_sort_idx: ForallSortIdx) -> Self { - AliasType { forall_sort_idx } + pub fn new(forall_sort_idx: ForallSortIdx, args: Vec>) -> Self { + AliasType { + forall_sort_idx, + args, + } } pub fn forall_sort_index(&self) -> ForallSortIdx { self.forall_sort_idx } + + pub fn args(&self) -> &[Type] { + &self.args + } } /// An array type. @@ -1163,10 +1191,13 @@ impl Type { pub fn free_ty_params(&self) -> HashSet { match self { - Type::Int | Type::Bool | Type::String | Type::Never | Type::Alias(_) => { - Default::default() - } + Type::Int | Type::Bool | Type::String | Type::Never => Default::default(), Type::Param(ty) => std::iter::once(ty.type_param_index()).collect(), + Type::Alias(ty) => ty + .args() + .iter() + .flat_map(|ty| ty.free_ty_params()) + .collect(), Type::Pointer(ty) => ty.free_ty_params(), Type::Function(ty) => ty.free_ty_params(), Type::Tuple(ty) => ty.free_ty_params(), @@ -1731,7 +1762,7 @@ impl RefinedType { { self.refinement.subst_ty_params_in_sorts(subst); match &mut self.ty { - Type::Int | Type::Bool | Type::String | Type::Never | Type::Alias(_) => {} + Type::Int | Type::Bool | Type::String | Type::Never => {} Type::Param(ty) => { if let Some(rty) = subst.get(ty.type_param_index()) { let RefinedType { @@ -1742,6 +1773,19 @@ impl RefinedType { self.ty = replacement_ty; } } + Type::Alias(alias) => { + let subst_closed = subst.clone().strip_refinement(); + let new_args: Vec> = alias + .args() + .iter() + .map(|arg| { + let mut arg_rty = RefinedType::unrefined(arg.clone()); + arg_rty.subst_ty_params(&subst_closed); + arg_rty.ty + }) + .collect(); + self.ty = Type::Alias(AliasType::new(alias.forall_sort_index(), new_args)); + } Type::Pointer(ty) => ty.subst_ty_params(subst), Type::Function(ty) => { let subst = subst.clone().strip_refinement(); @@ -1789,6 +1833,24 @@ impl RefinedType { (Type::Tuple(ty1), Type::Tuple(ty2)) => ty1.unify_ty_params(ty2), (Type::Array(ty1), Type::Array(ty2)) => ty1.unify_ty_params(ty2), (Type::Enum(ty1), Type::Enum(ty2)) => ty1.unify_ty_params(ty2), + (Type::Alias(a1), Type::Alias(a2)) + if a1.forall_sort_index() == a2.forall_sort_index() => + { + assert_eq!(a1.args().len(), a2.args().len()); + let args1: Vec> = a1 + .args() + .iter() + .cloned() + .map(|ty| RefinedType::unrefined(ty).vacuous()) + .collect(); + let args2: Vec> = a2 + .args() + .iter() + .cloned() + .map(|ty| RefinedType::unrefined(ty).vacuous()) + .collect(); + unify_tys_params(args1, args2) + } (t1, t2) => panic!("unify_ty_params: mismatched types t1={:?}, t2={:?}", t1, t2), } } diff --git a/src/rty/subtyping.rs b/src/rty/subtyping.rs index 72ec7df6..71196fde 100644 --- a/src/rty/subtyping.rs +++ b/src/rty/subtyping.rs @@ -125,6 +125,8 @@ where } (Type::Param(got), Type::Param(expected)) if got.forall_sort_idx == expected.forall_sort_idx => {} + (Type::Alias(got), Type::Alias(expected)) + if got.forall_sort_index() == expected.forall_sort_index() => {} _ => panic!( "inconsistent types: got={}, expected={}", got.display(), From c4cb3bcd3566e6b4aecdef70629034276aafed1b Mon Sep 17 00:00:00 2001 From: coeff-aij <175928954+coeff-aij@users.noreply.github.com> Date: Sun, 14 Jun 2026 04:35:29 +0900 Subject: [PATCH 049/142] add: opaque type handling for Box and Vec --- src/analyze/did_cache.rs | 4 ++++ src/refine/template.rs | 34 ++++++++++++++++++++++++++++++++++ 2 files changed, 38 insertions(+) diff --git a/src/analyze/did_cache.rs b/src/analyze/did_cache.rs index ee08a576..045b2ba1 100644 --- a/src/analyze/did_cache.rs +++ b/src/analyze/did_cache.rs @@ -53,6 +53,10 @@ impl<'tcx> DefIdCache<'tcx> { self.tcx.lang_items().owned_box() } + pub fn vec(&self) -> Option { + self.tcx.get_diagnostic_item(Symbol::intern("Vec")) + } + pub fn unique(&self) -> Option { *self.def_ids.unique.get_or_init(|| { let box_did = self.box_()?; diff --git a/src/refine/template.rs b/src/refine/template.rs index 9deff0b4..6ed54d3c 100644 --- a/src/refine/template.rs +++ b/src/refine/template.rs @@ -320,6 +320,23 @@ impl<'tcx> TypeBuilder<'tcx> { if let Some(model_ty) = self.model_adt(def, params) { return model_ty; } + // Treat Box and Vec as opaque types to avoid traversing internal structure + if Some(def.did()) == self.def_ids.box_() { + let elem_ty = self.build(params.type_at(0)); + return rty::PointerType::own(elem_ty).into(); + } + if Some(def.did()) == self.def_ids.vec() { + let elem_ty = self.build(params.type_at(0)); + // Vec is represented as a tuple of (Array, Int) in the model + let idx_ty = rty::Type::int(); + let array_ty = rty::ArrayType::new(idx_ty, elem_ty.clone()); + let len_ty = rty::Type::int(); + return rty::TupleType::new(vec![ + rty::PointerType::own(rty::Type::Array(array_ty)).into(), + rty::PointerType::own(len_ty).into(), + ]) + .into(); + } if def.is_enum() { let sym = refine::datatype_symbol(self.tcx, def.did()); let args: IndexVec<_, _> = params @@ -521,6 +538,23 @@ where if let Some(model_ty) = self.model_adt(def, params) { return model_ty; } + // Treat Box and Vec as opaque types to avoid traversing internal structure + if Some(def.did()) == self.inner.def_ids.box_() { + let elem_ty = self.build(params.type_at(0)); + return rty::PointerType::own(elem_ty).into(); + } + if Some(def.did()) == self.inner.def_ids.vec() { + let elem_ty = self.build(params.type_at(0)); + // Vec is represented as a tuple of (Array, Int) in the model + let idx_ty = rty::Type::int(); + let array_ty = rty::ArrayType::new(idx_ty, elem_ty.clone()); + let len_ty = rty::Type::int(); + return rty::TupleType::new(vec![ + rty::PointerType::own(rty::Type::Array(array_ty)).into(), + rty::PointerType::own(len_ty).into(), + ]) + .into(); + } if def.is_enum() { let sym = refine::datatype_symbol(self.inner.tcx, def.did()); let args: IndexVec<_, _> = From b6db7540e8aaa712d20ed38aae8cc6e6ddc22c8a Mon Sep 17 00:00:00 2001 From: coeff-aij <175928954+coeff-aij@users.noreply.github.com> Date: Sun, 14 Jun 2026 04:36:05 +0900 Subject: [PATCH 050/142] fix: use try_normalize_erasing_regions to avoid panic --- src/analyze/annot_fn.rs | 4 +++- src/analyze/local_def.rs | 3 ++- 2 files changed, 5 insertions(+), 2 deletions(-) diff --git a/src/analyze/annot_fn.rs b/src/analyze/annot_fn.rs index 07966a40..46a9aa23 100644 --- a/src/analyze/annot_fn.rs +++ b/src/analyze/annot_fn.rs @@ -256,7 +256,9 @@ impl<'a, 'tcx> AnnotFnTranslator<'a, 'tcx> { .instantiate_generics(ty, self.generic_args) .unwrap_or(ty); let typing_env = mir_ty::TypingEnv::fully_monomorphized(); - self.tcx.normalize_erasing_regions(typing_env, instantiated) + self.tcx + .try_normalize_erasing_regions(typing_env, instantiated) + .unwrap_or(instantiated) } pub fn to_formula_fn(&self) -> FormulaFn<'tcx> { diff --git a/src/analyze/local_def.rs b/src/analyze/local_def.rs index a5d02123..264240c8 100644 --- a/src/analyze/local_def.rs +++ b/src/analyze/local_def.rs @@ -399,7 +399,8 @@ impl<'tcx, 'ctx> Analyzer<'tcx, 'ctx> { for input_ty in sig.inputs() { let inst = self .tcx - .normalize_erasing_regions(mir_ty::TypingEnv::fully_monomorphized(), *input_ty); + .try_normalize_erasing_regions(mir_ty::TypingEnv::fully_monomorphized(), *input_ty) + .unwrap_or(*input_ty); let (fn_def_id, fn_args) = match inst.kind() { mir_ty::TyKind::Closure(def_id, args) => { (*def_id, self.tcx.mk_args(args.as_closure().parent_args())) From b1ad064f626b107a9038b522fef1cf876c0c7c13 Mon Sep 17 00:00:00 2001 From: coeff-aij <175928954+coeff-aij@users.noreply.github.com> Date: Sun, 14 Jun 2026 04:34:25 +0900 Subject: [PATCH 051/142] fix: relax &mut restriction to allow any element type --- src/refine/template.rs | 6 ------ 1 file changed, 6 deletions(-) diff --git a/src/refine/template.rs b/src/refine/template.rs index 6ed54d3c..377bcb28 100644 --- a/src/refine/template.rs +++ b/src/refine/template.rs @@ -290,9 +290,6 @@ impl<'tcx> TypeBuilder<'tcx> { } mir_ty::TyKind::Ref(_, elem_ty, mir_ty::Mutability::Mut) => { let elem_ty = self.build(*elem_ty); - if !matches!(elem_ty, rty::Type::Param(_)) { - panic!("unsupported mutable reference type: {elem_ty:?}"); - } rty::PointerType::mut_to(elem_ty).into() } mir_ty::TyKind::Tuple(ts) => { @@ -513,9 +510,6 @@ where } mir_ty::TyKind::Ref(_, elem_ty, mir_ty::Mutability::Mut) => { let elem_ty = self.build(*elem_ty); - if !matches!(elem_ty, rty::Type::Param(_)) { - panic!("unsupported mutable reference type: {elem_ty:?}"); - } rty::PointerType::mut_to(elem_ty).into() } mir_ty::TyKind::Tuple(ts) => { From 2077ab732140961aad301b792b4e856f4edaf6ac Mon Sep 17 00:00:00 2001 From: coeff-aij <175928954+coeff-aij@users.noreply.github.com> Date: Sun, 14 Jun 2026 21:33:36 +0900 Subject: [PATCH 052/142] revert: commenting out most of extern_spec in std.rs --- src/analyze/crate_.rs | 3 +- std.rs | 490 +++++++++++++++++++++--------------------- 2 files changed, 246 insertions(+), 247 deletions(-) diff --git a/src/analyze/crate_.rs b/src/analyze/crate_.rs index f2b19802..2188b099 100644 --- a/src/analyze/crate_.rs +++ b/src/analyze/crate_.rs @@ -183,8 +183,7 @@ impl<'tcx, 'ctx> Analyzer<'tcx, 'ctx> { let param = generics.param_at(idx, self.tcx); let arg = match param.kind { mir_ty::GenericParamDefKind::Type { .. } => { - let new_param = - mir_ty::Ty::new_param(self.tcx, param.index, param.name).into(); + let new_param = mir_ty::Ty::new_param(self.tcx, param.index, param.name).into(); tracing::debug!( "replace the cosnstrained param {:#?} with the new param {:#?}.", param, diff --git a/std.rs b/std.rs index 35d1a4c2..ef846777 100644 --- a/std.rs +++ b/std.rs @@ -333,35 +333,35 @@ mod thrust_models { } } -// #[thrust::extern_spec_fn] -// #[thrust_macros::requires(true)] -// #[thrust_macros::ensures(result == thrust_models::model::Box::new(x))] -// fn _extern_spec_box_new(x: T) -> Box where T: thrust_models::Model, T::Ty: PartialEq { -// Box::new(x) -// } +#[thrust::extern_spec_fn] +#[thrust_macros::requires(true)] +#[thrust_macros::ensures(result == thrust_models::model::Box::new(x))] +fn _extern_spec_box_new(x: T) -> Box where T: thrust_models::Model, T::Ty: PartialEq { + Box::new(x) +} -// #[thrust::extern_spec_fn] -// #[thrust_macros::requires(true)] -// #[thrust_macros::ensures(result == (x == y))] -// fn _extern_spec_box_partialeq_eq(x: &Box, y: &Box) -> bool -// where T: thrust_models::Model + PartialEq, T::Ty: PartialEq -// { -// as PartialEq>::eq(x, y) -// } +#[thrust::extern_spec_fn] +#[thrust_macros::requires(true)] +#[thrust_macros::ensures(result == (x == y))] +fn _extern_spec_box_partialeq_eq(x: &Box, y: &Box) -> bool + where T: thrust_models::Model + PartialEq, T::Ty: PartialEq +{ + as PartialEq>::eq(x, y) +} -// #[thrust::extern_spec_fn] -// #[thrust_macros::requires(true)] -// #[thrust_macros::ensures(*x == !y && *y == !x)] -// fn _extern_spec_std_mem_swap(x: &mut T, y: &mut T) where T: thrust_models::Model, T::Ty: PartialEq { -// std::mem::swap(x, y) -// } +#[thrust::extern_spec_fn] +#[thrust_macros::requires(true)] +#[thrust_macros::ensures(*x == !y && *y == !x)] +fn _extern_spec_std_mem_swap(x: &mut T, y: &mut T) where T: thrust_models::Model, T::Ty: PartialEq { + std::mem::swap(x, y) +} -// #[thrust::extern_spec_fn] -// #[thrust_macros::requires(true)] -// #[thrust_macros::ensures(!dest == src && result == *dest)] -// fn _extern_spec_std_mem_replace(dest: &mut T, src: T) -> T where T: thrust_models::Model, T::Ty: PartialEq { -// std::mem::replace(dest, src) -// } +#[thrust::extern_spec_fn] +#[thrust_macros::requires(true)] +#[thrust_macros::ensures(!dest == src && result == *dest)] +fn _extern_spec_std_mem_replace(dest: &mut T, src: T) -> T where T: thrust_models::Model, T::Ty: PartialEq { + std::mem::replace(dest, src) +} #[thrust::extern_spec_fn] #[thrust_macros::requires(true)] @@ -409,23 +409,23 @@ fn _extern_spec_option_unwrap_or(opt: Option, default: T) -> T where T: th Option::unwrap_or(opt, default) } -// #[thrust::extern_spec_fn] -// #[thrust_macros::requires( -// opt == None || thrust_models::exists(|i| opt == Some(i) && thrust_macros::pre!(f(i))) -// )] -// #[thrust_macros::ensures( -// (opt == None && result == None) -// || thrust_models::exists(|i| thrust_models::exists(|j| -// opt == Some(i) && thrust_macros::post!(f(i), j) && result == Some(j))) -// )] -// fn _extern_spec_option_map(opt: Option, f: F) -> Option -// where -// T: thrust_models::Model, T::Ty: PartialEq, -// U: thrust_models::Model, U::Ty: PartialEq, -// F: FnOnce(T) -> U, -// { -// Option::map(opt, f) -// } +#[thrust::extern_spec_fn] +#[thrust_macros::requires( + opt == None || thrust_models::exists(|i| opt == Some(i) && thrust_macros::pre!(f(i))) +)] +#[thrust_macros::ensures( + (opt == None && result == None) + || thrust_models::exists(|i| thrust_models::exists(|j| + opt == Some(i) && thrust_macros::post!(f(i), j) && result == Some(j))) +)] +fn _extern_spec_option_map(opt: Option, f: F) -> Option +where + T: thrust_models::Model, T::Ty: PartialEq, + U: thrust_models::Model, U::Ty: PartialEq, + F: FnOnce(T) -> U, +{ + Option::map(opt, f) +} // #[thrust::extern_spec_fn] // #[thrust_macros::requires(opt != None || thrust_macros::pre!(f()))] @@ -441,64 +441,64 @@ fn _extern_spec_option_unwrap_or(opt: Option, default: T) -> T where T: th // Option::unwrap_or_else(opt, f) // } -// #[thrust::extern_spec_fn] -// #[thrust_macros::requires(true)] -// #[thrust_macros::ensures( -// (thrust_models::exists(|x| opt == Some(x) && result == Ok(x))) -// || (opt == None && result == Err(err)) -// )] -// fn _extern_spec_option_ok_or(opt: Option, err: E) -> Result -// where T: thrust_models::Model, T::Ty: PartialEq, -// E: thrust_models::Model, E::Ty: PartialEq, -// { -// Option::ok_or(opt, err) -// } +#[thrust::extern_spec_fn] +#[thrust_macros::requires(true)] +#[thrust_macros::ensures( + (thrust_models::exists(|x| opt == Some(x) && result == Ok(x))) + || (opt == None && result == Err(err)) +)] +fn _extern_spec_option_ok_or(opt: Option, err: E) -> Result + where T: thrust_models::Model, T::Ty: PartialEq, + E: thrust_models::Model, E::Ty: PartialEq, +{ + Option::ok_or(opt, err) +} -// #[thrust::extern_spec_fn] -// #[thrust_macros::requires(true)] -// #[thrust_macros::ensures(!opt == None && result == *opt)] -// fn _extern_spec_option_take(opt: &mut Option) -> Option where T: thrust_models::Model, T::Ty: PartialEq { -// Option::take(opt) -// } +#[thrust::extern_spec_fn] +#[thrust_macros::requires(true)] +#[thrust_macros::ensures(!opt == None && result == *opt)] +fn _extern_spec_option_take(opt: &mut Option) -> Option where T: thrust_models::Model, T::Ty: PartialEq { + Option::take(opt) +} -// #[thrust::extern_spec_fn] -// #[thrust_macros::requires(true)] -// #[thrust_macros::ensures(!opt == Some(src) && result == *opt)] -// fn _extern_spec_option_replace(opt: &mut Option, src: T) -> Option -// where T: thrust_models::Model, T::Ty: PartialEq -// { -// Option::replace(opt, src) -// } +#[thrust::extern_spec_fn] +#[thrust_macros::requires(true)] +#[thrust_macros::ensures(!opt == Some(src) && result == *opt)] +fn _extern_spec_option_replace(opt: &mut Option, src: T) -> Option + where T: thrust_models::Model, T::Ty: PartialEq +{ + Option::replace(opt, src) +} -// #[thrust::extern_spec_fn] -// #[thrust_macros::requires(true)] -// #[thrust_macros::ensures( -// thrust_models::exists(|x| opt == &Some(x) && result == Some(&x)) -// || (opt == &None && result == None) -// )] -// fn _extern_spec_option_as_ref(opt: &Option) -> Option<&T> where T: thrust_models::Model, T::Ty: PartialEq { -// Option::as_ref(opt) -// } +#[thrust::extern_spec_fn] +#[thrust_macros::requires(true)] +#[thrust_macros::ensures( + thrust_models::exists(|x| opt == &Some(x) && result == Some(&x)) + || (opt == &None && result == None) +)] +fn _extern_spec_option_as_ref(opt: &Option) -> Option<&T> where T: thrust_models::Model, T::Ty: PartialEq { + Option::as_ref(opt) +} -// #[thrust::extern_spec_fn] -// #[thrust_macros::requires(true)] -// #[thrust_macros::ensures( -// thrust_models::exists(|x1, x2| -// *opt == Some(x1) && -// !opt == Some(x2) && -// result == Some(thrust_models::model::Mut::new(x1, x2)) -// ) -// || ( -// *opt == None && -// !opt == None && -// result == None -// ) -// )] -// fn _extern_spec_option_as_mut(opt: &mut Option) -> Option<&mut T> -// where T: thrust_models::Model, T::Ty: PartialEq -// { -// Option::as_mut(opt) -// } +#[thrust::extern_spec_fn] +#[thrust_macros::requires(true)] +#[thrust_macros::ensures( + thrust_models::exists(|x1, x2| + *opt == Some(x1) && + !opt == Some(x2) && + result == Some(thrust_models::model::Mut::new(x1, x2)) + ) + || ( + *opt == None && + !opt == None && + result == None + ) +)] +fn _extern_spec_option_as_mut(opt: &mut Option) -> Option<&mut T> + where T: thrust_models::Model, T::Ty: PartialEq +{ + Option::as_mut(opt) +} #[thrust::extern_spec_fn] #[thrust_macros::requires(true)] @@ -510,77 +510,77 @@ fn _extern_spec_result_partialeq_eq(x: &Result, y: &Result) -> as PartialEq>::eq(x, y) } -// #[thrust::extern_spec_fn] -// #[thrust_macros::requires(thrust_models::exists(|x| res == Ok(x)))] -// #[thrust_macros::ensures(Ok(result) == res)] -// fn _extern_spec_result_unwrap(res: Result) -> T -// where T: thrust_models::Model, T::Ty: PartialEq, -// E: thrust_models::Model, E::Ty: PartialEq, -// { -// Result::unwrap(res) -// } +#[thrust::extern_spec_fn] +#[thrust_macros::requires(thrust_models::exists(|x| res == Ok(x)))] +#[thrust_macros::ensures(Ok(result) == res)] +fn _extern_spec_result_unwrap(res: Result) -> T + where T: thrust_models::Model, T::Ty: PartialEq, + E: thrust_models::Model, E::Ty: PartialEq, +{ + Result::unwrap(res) +} -// #[thrust::extern_spec_fn] -// #[thrust_macros::requires(thrust_models::exists(|x| res == Err(x)))] -// #[thrust_macros::ensures(Err(result) == res)] -// fn _extern_spec_result_unwrap_err(res: Result) -> E -// where T: thrust_models::Model, T::Ty: PartialEq, -// E: thrust_models::Model, E::Ty: PartialEq, -// { -// Result::unwrap_err(res) -// } +#[thrust::extern_spec_fn] +#[thrust_macros::requires(thrust_models::exists(|x| res == Err(x)))] +#[thrust_macros::ensures(Err(result) == res)] +fn _extern_spec_result_unwrap_err(res: Result) -> E + where T: thrust_models::Model, T::Ty: PartialEq, + E: thrust_models::Model, E::Ty: PartialEq, +{ + Result::unwrap_err(res) +} -// #[thrust::extern_spec_fn] -// #[thrust_macros::requires(true)] -// #[thrust_macros::ensures( -// thrust_models::exists(|x| res == Ok(x) && result == Some(x)) -// || thrust_models::exists(|x| res == Err(x) && result == None) -// )] -// fn _extern_spec_result_ok(res: Result) -> Option -// where T: thrust_models::Model, T::Ty: PartialEq, -// E: thrust_models::Model, E::Ty: PartialEq, -// { -// Result::ok(res) -// } +#[thrust::extern_spec_fn] +#[thrust_macros::requires(true)] +#[thrust_macros::ensures( + thrust_models::exists(|x| res == Ok(x) && result == Some(x)) + || thrust_models::exists(|x| res == Err(x) && result == None) +)] +fn _extern_spec_result_ok(res: Result) -> Option + where T: thrust_models::Model, T::Ty: PartialEq, + E: thrust_models::Model, E::Ty: PartialEq, +{ + Result::ok(res) +} -// #[thrust::extern_spec_fn] -// #[thrust_macros::requires(true)] -// #[thrust_macros::ensures( -// thrust_models::exists(|x| res == Ok(x) && result == None) -// || thrust_models::exists(|x| res == Err(x) && result == Some(x)) -// )] -// fn _extern_spec_result_err(res: Result) -> Option -// where T: thrust_models::Model, T::Ty: PartialEq, -// E: thrust_models::Model, E::Ty: PartialEq, -// { -// Result::err(res) -// } +#[thrust::extern_spec_fn] +#[thrust_macros::requires(true)] +#[thrust_macros::ensures( + thrust_models::exists(|x| res == Ok(x) && result == None) + || thrust_models::exists(|x| res == Err(x) && result == Some(x)) +)] +fn _extern_spec_result_err(res: Result) -> Option + where T: thrust_models::Model, T::Ty: PartialEq, + E: thrust_models::Model, E::Ty: PartialEq, +{ + Result::err(res) +} -// #[thrust::extern_spec_fn] -// #[thrust_macros::requires(true)] -// #[thrust_macros::ensures( -// thrust_models::exists(|x| *res == Ok(x) && result == true) -// || thrust_models::exists(|x| *res == Err(x) && result == false) -// )] -// fn _extern_spec_result_is_ok(res: &Result) -> bool -// where T: thrust_models::Model, T::Ty: PartialEq, -// E: thrust_models::Model, E::Ty: PartialEq, -// { -// Result::is_ok(res) -// } +#[thrust::extern_spec_fn] +#[thrust_macros::requires(true)] +#[thrust_macros::ensures( + thrust_models::exists(|x| *res == Ok(x) && result == true) + || thrust_models::exists(|x| *res == Err(x) && result == false) +)] +fn _extern_spec_result_is_ok(res: &Result) -> bool + where T: thrust_models::Model, T::Ty: PartialEq, + E: thrust_models::Model, E::Ty: PartialEq, +{ + Result::is_ok(res) +} -// #[thrust::extern_spec_fn] -// #[thrust_macros::requires(true)] -// #[thrust_macros::ensures( -// thrust_models::exists(|x| *res == Ok(x) && result == false) -// || thrust_models::exists(|x| *res == Err(x) && result == true) -// )] -// fn _extern_spec_result_is_err(res: &Result) -> bool -// where T: thrust_models::Model, T::Ty: PartialEq, -// E: thrust_models::Model, E::Ty: PartialEq, -// { -// Result::is_err(res) -// } +#[thrust::extern_spec_fn] +#[thrust_macros::requires(true)] +#[thrust_macros::ensures( + thrust_models::exists(|x| *res == Ok(x) && result == false) + || thrust_models::exists(|x| *res == Err(x) && result == true) +)] +fn _extern_spec_result_is_err(res: &Result) -> bool + where T: thrust_models::Model, T::Ty: PartialEq, + E: thrust_models::Model, E::Ty: PartialEq, +{ + Result::is_err(res) +} #[thrust::extern_spec_fn] #[thrust_macros::requires(true)] // TODO: require x != i32::MIN @@ -620,96 +620,96 @@ fn _extern_spec_i32_is_negative(x: i32) -> bool { i32::is_negative(x) } -// #[thrust::extern_spec_fn] -// #[thrust_macros::requires(true)] -// #[thrust_macros::ensures(result.1 == 0)] -// fn _extern_spec_vec_new() -> Vec where T: thrust_models::Model, T::Ty: PartialEq { -// Vec::::new() -// } +#[thrust::extern_spec_fn] +#[thrust_macros::requires(true)] +#[thrust_macros::ensures(result.1 == 0)] +fn _extern_spec_vec_new() -> Vec where T: thrust_models::Model, T::Ty: PartialEq { + Vec::::new() +} -// #[thrust::extern_spec_fn] -// #[thrust_macros::requires(true)] -// #[thrust_macros::ensures(!vec == thrust_models::model::Vec((*vec).0.store((*vec).1, elem), (*vec).1 + 1))] -// fn _extern_spec_vec_push(vec: &mut Vec, elem: T) -// where T: thrust_models::Model, T::Ty: PartialEq -// { -// Vec::push(vec, elem) -// } +#[thrust::extern_spec_fn] +#[thrust_macros::requires(true)] +#[thrust_macros::ensures(!vec == thrust_models::model::Vec((*vec).0.store((*vec).1, elem), (*vec).1 + 1))] +fn _extern_spec_vec_push(vec: &mut Vec, elem: T) + where T: thrust_models::Model, T::Ty: PartialEq +{ + Vec::push(vec, elem) +} -// #[thrust::extern_spec_fn] -// #[thrust_macros::requires(true)] -// #[thrust_macros::ensures(result == vec.1)] -// fn _extern_spec_vec_len(vec: &Vec) -> usize where T: thrust_models::Model, T::Ty: PartialEq { -// Vec::len(vec) -// } +#[thrust::extern_spec_fn] +#[thrust_macros::requires(true)] +#[thrust_macros::ensures(result == vec.1)] +fn _extern_spec_vec_len(vec: &Vec) -> usize where T: thrust_models::Model, T::Ty: PartialEq { + Vec::len(vec) +} -// #[thrust::extern_spec_fn] -// #[thrust_macros::requires(index < vec.1)] -// #[thrust_macros::ensures(*result == vec.0[index])] -// fn _extern_spec_vec_index(vec: &Vec, index: usize) -> &T where T: thrust_models::Model, T::Ty: PartialEq { -// as std::ops::Index>::index(vec, index) -// } +#[thrust::extern_spec_fn] +#[thrust_macros::requires(index < vec.1)] +#[thrust_macros::ensures(*result == vec.0[index])] +fn _extern_spec_vec_index(vec: &Vec, index: usize) -> &T where T: thrust_models::Model, T::Ty: PartialEq { + as std::ops::Index>::index(vec, index) +} -// #[thrust::extern_spec_fn] -// #[thrust_macros::requires(index < (*vec).1)] -// #[thrust_macros::ensures( -// *result == (*vec).0[index] && -// !result == (!vec).0[index] && -// !vec == thrust_models::model::Vec((*vec).0.store(index, !result), (*vec).1) -// )] -// fn _extern_spec_vec_index_mut(vec: &mut Vec, index: usize) -> &mut T -// where T: thrust_models::Model, T::Ty: PartialEq -// { -// as std::ops::IndexMut>::index_mut(vec, index) -// } +#[thrust::extern_spec_fn] +#[thrust_macros::requires(index < (*vec).1)] +#[thrust_macros::ensures( + *result == (*vec).0[index] && + !result == (!vec).0[index] && + !vec == thrust_models::model::Vec((*vec).0.store(index, !result), (*vec).1) +)] +fn _extern_spec_vec_index_mut(vec: &mut Vec, index: usize) -> &mut T + where T: thrust_models::Model, T::Ty: PartialEq +{ + as std::ops::IndexMut>::index_mut(vec, index) +} -// #[thrust::extern_spec_fn] -// #[thrust_macros::requires(true)] -// #[thrust_macros::ensures((!vec).1 == 0)] -// fn _extern_spec_vec_clear(vec: &mut Vec) where T: thrust_models::Model, T::Ty: PartialEq { -// Vec::clear(vec) -// } +#[thrust::extern_spec_fn] +#[thrust_macros::requires(true)] +#[thrust_macros::ensures((!vec).1 == 0)] +fn _extern_spec_vec_clear(vec: &mut Vec) where T: thrust_models::Model, T::Ty: PartialEq { + Vec::clear(vec) +} -// #[thrust::extern_spec_fn] -// #[thrust_macros::requires(true)] -// #[thrust_macros::ensures( -// (!vec).0 == (*vec).0 && ( -// ( -// (*vec).1 > 0 && -// (!vec).1 == (*vec).1 - 1 && -// result == Some((*vec).0[(*vec).1 - 1]) -// ) || ( -// (*vec).1 == 0 && -// (!vec).1 == 0 && -// result == None -// ) -// ) -// )] -// fn _extern_spec_vec_pop(vec: &mut Vec) -> Option where T: thrust_models::Model, T::Ty: PartialEq { -// Vec::pop(vec) -// } +#[thrust::extern_spec_fn] +#[thrust_macros::requires(true)] +#[thrust_macros::ensures( + (!vec).0 == (*vec).0 && ( + ( + (*vec).1 > 0 && + (!vec).1 == (*vec).1 - 1 && + result == Some((*vec).0[(*vec).1 - 1]) + ) || ( + (*vec).1 == 0 && + (!vec).1 == 0 && + result == None + ) + ) +)] +fn _extern_spec_vec_pop(vec: &mut Vec) -> Option where T: thrust_models::Model, T::Ty: PartialEq { + Vec::pop(vec) +} -// #[thrust::extern_spec_fn] -// #[thrust_macros::requires(true)] -// #[thrust_macros::ensures(result == ((*vec).1 == 0))] -// fn _extern_spec_vec_is_empty(vec: &Vec) -> bool where T: thrust_models::Model, T::Ty: PartialEq { -// Vec::is_empty(vec) -// } +#[thrust::extern_spec_fn] +#[thrust_macros::requires(true)] +#[thrust_macros::ensures(result == ((*vec).1 == 0))] +fn _extern_spec_vec_is_empty(vec: &Vec) -> bool where T: thrust_models::Model, T::Ty: PartialEq { + Vec::is_empty(vec) +} -// #[thrust::extern_spec_fn] -// #[thrust_macros::requires(true)] -// #[thrust_macros::ensures( -// ( -// (*vec).1 > len && -// !vec == thrust_models::model::Vec((*vec).0, len) -// ) || ( -// (*vec).1 <= len && -// !vec == *vec -// ) -// )] -// fn _extern_spec_vec_truncate(vec: &mut Vec, len: usize) where T: thrust_models::Model, T::Ty: PartialEq { -// Vec::truncate(vec, len) -// } +#[thrust::extern_spec_fn] +#[thrust_macros::requires(true)] +#[thrust_macros::ensures( + ( + (*vec).1 > len && + !vec == thrust_models::model::Vec((*vec).0, len) + ) || ( + (*vec).1 <= len && + !vec == *vec + ) +)] +fn _extern_spec_vec_truncate(vec: &mut Vec, len: usize) where T: thrust_models::Model, T::Ty: PartialEq { + Vec::truncate(vec, len) +} // TODO: The following specs of some trait methods are too restrictive; we should allow for a // per-impl spec once we can describe the spec of blanket impls. From 92764b7d093df34da3a1120f80ff733a5c8f8d97 Mon Sep 17 00:00:00 2001 From: coeff-aij <175928954+coeff-aij@users.noreply.github.com> Date: Sun, 14 Jun 2026 21:41:30 +0900 Subject: [PATCH 053/142] add: annotations for fold() (WIP) --- tests/ui/pass/traits/fold.rs | 116 +++++++++++++++++++++++++++++++++++ 1 file changed, 116 insertions(+) create mode 100644 tests/ui/pass/traits/fold.rs diff --git a/tests/ui/pass/traits/fold.rs b/tests/ui/pass/traits/fold.rs new file mode 100644 index 00000000..a30491a7 --- /dev/null +++ b/tests/ui/pass/traits/fold.rs @@ -0,0 +1,116 @@ +//@check-pass +//@compile-flags: -C debug-assertions=off +//@rustc-env: THRUST_SOLVER=tests/thrust-pcsat-wrapper THRUST_SOLVER_TIMEOUT_SECS=60 + +#[thrust_macros::context] +trait Iterator { + type Item; + + #[thrust_macros::ensures( + Self::completed(*self) + || thrust_models::exists(|i| (result == Some(i)) && Self::step(*self, i, !self)) + )] + #[thrust_macros::ensures(!Self::completed(*self) || (result == None && *self == !self))] + fn next(&mut self) -> Option; + + #[thrust_macros::predicate] + fn completed(self) -> bool; + #[thrust_macros::predicate] + fn step(self, item: Self::Item, dist: Self) -> bool; + + #[thrust_macros::requires(true)] + #[thrust_macros::ensures( + // ∃ it: Vec. (it.0 : Array, it.1 : Int) + thrust_models::exists(|it: thrust_models::model::Vec| + // ∃ acc: Vec. (acc.0 : Array, acc.1 : Int) + thrust_models::exists(|acc| + it.0[0] == *self && + acc.0[0] == init && + Self::completed(it.0[it.1 - 1]) && + result == acc.0[it.1 - 1] && + // ∀ i. (0 ≤ i ∧ i < it.1 - 1) ⟹ + // ∃ item. Self::step(it[i], item, it[i+1]) + // ∧ post!(f(acc[i], item), acc[i+1]) + // !(∃ i. (0 ≤ i ∧ i < it.1 - 1) ∧ !(∃ item. step ∧ post)) + !( + thrust_models::exists(|i| + (0 <= i && i < it.1 - 1) && + !( + thrust_models::exists(|item| + Self::step(it.0[i], item, it.0[i + 1]) && + thrust_macros::post!( + f(acc.0[i], item), + acc.0[i + 1] + ) + ) + ) + ) + ) + )) + )] + fn fold(mut self, init: B, mut f: F) -> B + where + Self: Sized, + F: FnMut(B, Self::Item) -> B, + { + let mut accum = init; + while let Some(x) = self.next() { + accum = f(accum, x); + } + accum + } +} + +struct Range { + start: i64, + end: i64, +} + +impl thrust_models::Model for Range { + type Ty = Range; +} + +#[thrust_macros::context] +impl Iterator for Range { + type Item = i64; + + fn next(&mut self) -> Option { + if self.start < self.end { + let item = self.start; + self.start += 1; + Some(item) + } else { + None + } + } + + #[thrust_macros::predicate] + fn completed(self) -> bool { + // (tuple_proj.0 self) is equivalent to self.start + // !(self.start < self.end) is written as following: + "(not (< + (tuple_proj.0 self_) + (tuple_proj.1 self_) + ))"; + true + } + + #[thrust_macros::predicate] + fn step(self, item: Self::Item, dist: Self) -> bool { + // self.end == dist.end && self.start == item && self.start + 1 == dist.start + // is written as following: + "(and + (= (tuple_proj.1 self_) (tuple_proj.1 dist)) + (= (tuple_proj.0 self_) item) + (= (+ (tuple_proj.0 self_) 1) (tuple_proj.0 dist)) + )"; + true + } +} + +fn main() { + let mut range = Range { start: 0, end: 5 }; + let sum = range.fold(0, |x, y| x + y); + + assert!(sum == 10); +} \ No newline at end of file From 312f3623a223af4c91a7baeda8733085d52eec33 Mon Sep 17 00:00:00 2001 From: coeff-aij <175928954+coeff-aij@users.noreply.github.com> Date: Sun, 14 Jun 2026 23:03:47 +0900 Subject: [PATCH 054/142] fix annotations on fold() (WIP) --- tests/ui/pass/traits/fold.rs | 61 ++++++++++++++++++++++++++---------- 1 file changed, 45 insertions(+), 16 deletions(-) diff --git a/tests/ui/pass/traits/fold.rs b/tests/ui/pass/traits/fold.rs index a30491a7..2265ec43 100644 --- a/tests/ui/pass/traits/fold.rs +++ b/tests/ui/pass/traits/fold.rs @@ -2,13 +2,15 @@ //@compile-flags: -C debug-assertions=off //@rustc-env: THRUST_SOLVER=tests/thrust-pcsat-wrapper THRUST_SOLVER_TIMEOUT_SECS=60 +use thrust_models::exists; + #[thrust_macros::context] trait Iterator { type Item; #[thrust_macros::ensures( Self::completed(*self) - || thrust_models::exists(|i| (result == Some(i)) && Self::step(*self, i, !self)) + || exists(|i| (result == Some(i)) && Self::step(*self, i, !self)) )] #[thrust_macros::ensures(!Self::completed(*self) || (result == None && *self == !self))] fn next(&mut self) -> Option; @@ -18,26 +20,25 @@ trait Iterator { #[thrust_macros::predicate] fn step(self, item: Self::Item, dist: Self) -> bool; + #[thrust_macros::invariant_context] #[thrust_macros::requires(true)] #[thrust_macros::ensures( - // ∃ it: Vec. (it.0 : Array, it.1 : Int) - thrust_models::exists(|it: thrust_models::model::Vec| - // ∃ acc: Vec. (acc.0 : Array, acc.1 : Int) - thrust_models::exists(|acc| - it.0[0] == *self && + exists(|it: thrust_models::model::Vec| + exists(|fn_: thrust_models::model::Vec| + exists(|acc| + exists(|l: thrust_models::model::Int| + it.0[0] == self && acc.0[0] == init && - Self::completed(it.0[it.1 - 1]) && - result == acc.0[it.1 - 1] && - // ∀ i. (0 ≤ i ∧ i < it.1 - 1) ⟹ - // ∃ item. Self::step(it[i], item, it[i+1]) - // ∧ post!(f(acc[i], item), acc[i+1]) - // !(∃ i. (0 ≤ i ∧ i < it.1 - 1) ∧ !(∃ item. step ∧ post)) + Self::completed(it.0[l - 1]) && + result == acc.0[l - 1] && !( - thrust_models::exists(|i| - (0 <= i && i < it.1 - 1) && + exists(|i| + (0 <= i && i < l - 1) && !( - thrust_models::exists(|item| + exists(|item| + !Self::completed(it.0[i]) && Self::step(it.0[i], item, it.0[i + 1]) && + thrust_macros::pre!(f(acc.0[i], item)) && thrust_macros::post!( f(acc.0[i], item), acc.0[i + 1] @@ -46,7 +47,7 @@ trait Iterator { ) ) ) - )) + )))) )] fn fold(mut self, init: B, mut f: F) -> B where @@ -55,6 +56,34 @@ trait Iterator { { let mut accum = init; while let Some(x) = self.next() { + thrust_macros::invariant!( + |accum: B| + exists(|it: thrust_models::model::Vec| + exists(|fn_: thrust_models::model::Vec| + exists(|acc| + exists(|l: thrust_models::model::Int| + it.0[0] == self && + fn_.0[0] == f && + acc.0[0] == init && + accum == acc.0[l - 1] && + !( + exists(|i: thrust_models::model::Int| + (0 <= i && i < l - 1) && + !( + exists(|item: Self::Item| + !Self::completed(it.0[i]) && + Self::step(it.0[i], item, it.0[i + 1]) && + thrust_macros::pre!(f(acc.0[i], item)) && + thrust_macros::post!( + f(acc.0[i], item), + acc.0[i + 1] + ) + ) + ) + ) + ) + )))) + ); accum = f(accum, x); } accum From 848f511c3846f89b1a848e8bf8c120a8d5cbc24c Mon Sep 17 00:00:00 2001 From: coeff-aij <175928954+coeff-aij@users.noreply.github.com> Date: Sun, 14 Jun 2026 23:46:42 +0900 Subject: [PATCH 055/142] add test codes for reproducing/avoiding annotation errors --- .../ui/annot-error/array_index_literal_int.rs | 24 ++++++++ .../array_index_literal_int_workaround.rs | 27 +++++++++ .../annot-error/formula_fn_capture_local.rs | 47 +++++++++++++++ .../invariant_context_self_trait_bound.rs | 50 ++++++++++++++++ ...ant_context_self_trait_bound_workaround.rs | 60 +++++++++++++++++++ .../invariant_context_trait_method.rs | 27 +++++++++ ...variant_context_trait_method_workaround.rs | 49 +++++++++++++++ 7 files changed, 284 insertions(+) create mode 100644 tests/ui/annot-error/array_index_literal_int.rs create mode 100644 tests/ui/annot-error/array_index_literal_int_workaround.rs create mode 100644 tests/ui/annot-error/formula_fn_capture_local.rs create mode 100644 tests/ui/annot-error/invariant_context_self_trait_bound.rs create mode 100644 tests/ui/annot-error/invariant_context_self_trait_bound_workaround.rs create mode 100644 tests/ui/annot-error/invariant_context_trait_method.rs create mode 100644 tests/ui/annot-error/invariant_context_trait_method_workaround.rs diff --git a/tests/ui/annot-error/array_index_literal_int.rs b/tests/ui/annot-error/array_index_literal_int.rs new file mode 100644 index 00000000..bb64b171 --- /dev/null +++ b/tests/ui/annot-error/array_index_literal_int.rs @@ -0,0 +1,24 @@ +// Reproduces: an integer literal used as an `Array` index in a spec +// expression fails to type-check (E0308 "expected `Int`, found integer"). +// +// `thrust_models::model::Array` has an `Index` impl whose index +// type is the `I` parameter; for `Array` that is the `model::Int` +// ZST, which is not the same as Rust's `{integer}` literal type. The spec +// attribute path lowers `it[0]` as a Rust expression, so the `0` must be +// a `model::Int`-typed term. No such literal is constructible in Rust +// source today. +// +// See `array_index_literal_int_workaround.rs` for the bound-variable +// form that sidesteps the literal. + +#[thrust_macros::requires(true)] +#[thrust_macros::ensures( + thrust_models::exists(|it: thrust_models::model::Array| + it[0] == 0 + ) +)] +fn head(arr: Vec) -> i64 { + arr[0] +} + +fn main() {} diff --git a/tests/ui/annot-error/array_index_literal_int_workaround.rs b/tests/ui/annot-error/array_index_literal_int_workaround.rs new file mode 100644 index 00000000..74d74953 --- /dev/null +++ b/tests/ui/annot-error/array_index_literal_int_workaround.rs @@ -0,0 +1,27 @@ +// Annotation-side workaround for `array_index_literal_int.rs`. +// +// Rather than writing the literal `0` as the index (which fails because +// the `Index` impl on `Array` requires `I`-typed indices, and +// `model::Int` has no Rust literal form), bind the index with an +// existential and let typeck infer its sort: +// +// exists(|idx| it[idx] == 0) +// +// `idx` gets the `model::Int` sort from the `Index` site's expected +// `I = model::Int`. The expression type-checks; the trade-off is that +// the spec no longer pins a specific index like "0" or "1" — it just +// asserts "there exists some index such that the value at that index is 0". + +#[thrust_macros::requires(true)] +#[thrust_macros::ensures( + thrust_models::exists(|it: thrust_models::model::Array| + thrust_models::exists(|idx| + it[idx] == 0 + ) + ) +)] +fn head(arr: Vec) -> i64 { + arr[0] +} + +fn main() {} diff --git a/tests/ui/annot-error/formula_fn_capture_local.rs b/tests/ui/annot-error/formula_fn_capture_local.rs new file mode 100644 index 00000000..ba7ec28c --- /dev/null +++ b/tests/ui/annot-error/formula_fn_capture_local.rs @@ -0,0 +1,47 @@ +// Reproduces: a `formula_fn` produced by `requires`/`ensures`/`invariant!` +// cannot capture the surrounding function's local bindings (E0434 "can't +// capture dynamic environment in a fn item"). +// +// The macro lowers the spec into a free `fn _thrust_ensures_X(...)` whose +// only inputs are the host parameters (lowered to their `Model::Ty`) and +// the closure's bound variables. Any reference to a `let`-bound name in +// the host function is rejected. +// +// Same shape, applied to `_invariant_with_context!`: the host signature +// re-declared in the macro head (e.g. `fn run(self, f: B, g: F)`) +// is *not* threaded into the `formula_fn` parameters either; the macro +// currently only lowers the closure params plus the synthetic +// `__ThrustSelf` (for `Self` rewrite). +// +// No annotation-side workaround: this is a macro bug in +// `thrust-macros/src/invariant.rs::expand_invariant` (the host-signature +// re-declaration is parsed but its parameters are dropped on the floor). +// Either fix the macro to lower the re-declared signature's params via +// `type_lowering.lower_params(...)` and add them to the formula_fn, or +// restructure the invariant to not mention the host parameters (which +// often defeats the point of the invariant). + +#[thrust_macros::context] +trait Foo { + fn run(self, f: B, g: F) -> B + where + Self: Sized, + F: FnOnce(B) -> B, + { + let mut x: i64 = 0; + while x < 1 { + thrust_macros::_invariant_with_context!( + #[thrust::_outer_context(trait Foo {})] + fn run(self: Self, f: B, g: F) -> B + where + Self: Sized, + F: FnOnce(B) -> B; + |x: i64| x == f && g == f + ); + x += 1; + } + f + } +} + +fn main() {} diff --git a/tests/ui/annot-error/invariant_context_self_trait_bound.rs b/tests/ui/annot-error/invariant_context_self_trait_bound.rs new file mode 100644 index 00000000..25f9ca12 --- /dev/null +++ b/tests/ui/annot-error/invariant_context_self_trait_bound.rs @@ -0,0 +1,50 @@ +// Reproduces: when an `_invariant_with_context!` rewrites `Self` to a +// synthetic `__ThrustSelf` generic in the injected `formula_fn`, it does +// NOT automatically propagate the host trait bound (here `Self: Foo`). +// Calling trait items (the user-defined predicates `completed` / `step` +// and the associated `Item` type) on the synthetic `Self` therefore +// fails with E0599 / E0220 "no function / associated type named X found +// for `__ThrustSelf`". +// +// See `invariant_context_self_trait_bound_workaround.rs` for a partial +// workaround (`Self: Sized + Foo` in the re-declared where clause) that +// silences E0599 (the trait method calls) but leaves E0220 (the +// associated type) untouched — fully fixing the latter requires the +// `expand_invariant` macro to also rewrite `Self` to `__ThrustSelf` in +// the propagated where-clause predicates. + +#[thrust_macros::context] +trait Foo { + type Item; + + #[thrust_macros::predicate] + fn completed(self) -> bool; + #[thrust_macros::predicate] + fn step(self, item: Self::Item, dist: Self) -> bool; + + fn run(mut self, init: B, mut f: F) -> B + where + Self: Sized, + F: FnMut(B, Self::Item) -> B, + { + let mut accum = init; + while true { + thrust_macros::_invariant_with_context!( + #[thrust::_outer_context(trait Foo { type Item; })] + fn run(mut self: Self, init: B, mut f: F) -> B + where + Self: Sized, + F: FnMut(B, Self::Item) -> B; + |accum: B| thrust_models::exists( + |item: Self::Item| + Self::step(*self, item, *self) + && accum == init + ) + ); + break; + } + accum + } +} + +fn main() {} diff --git a/tests/ui/annot-error/invariant_context_self_trait_bound_workaround.rs b/tests/ui/annot-error/invariant_context_self_trait_bound_workaround.rs new file mode 100644 index 00000000..3fc07657 --- /dev/null +++ b/tests/ui/annot-error/invariant_context_self_trait_bound_workaround.rs @@ -0,0 +1,60 @@ +// Annotation-side partial workaround for +// `invariant_context_self_trait_bound.rs`. +// +// Adding the host trait bound to the re-declared signature's where +// clause (`Self: Sized + Foo` instead of just `Self: Sized`) makes the +// `expand_invariant` macro copy it into the `formula_fn`'s where +// clause, so `__ThrustSelf: Foo` is in scope. That silences the trait +// method-call errors (E0599 for `__ThrustSelf::step` / +// `__ThrustSelf::completed`). +// +// What it does NOT fix: E0220 for `__ThrustSelf::Item`. The macro's +// `where_predicates()` walk copies the re-declared where-clause +// predicates verbatim — `Self` is *not* rewritten to `__ThrustSelf` in +// the copied predicates, so the copy still talks about `Self` (host +// type) rather than `__ThrustSelf` (synthetic). The associated type +// `Item` lookup goes through `Self` instead of `__ThrustSelf`, and Rust +// complains. Fully fixing this needs the macro to rewrite `Self` to +// `__ThrustSelf` in the propagated where-clause predicates, then add +// `<__ThrustSelf as Foo>::Item` (or an analogous `Item` projection) to +// the `__ThrustSelf` parameter scope. + +#[thrust_macros::context] +trait Foo { + type Item; + + #[thrust_macros::predicate] + fn completed(self) -> bool; + #[thrust_macros::predicate] + fn step(self, item: Self::Item, dist: Self) -> bool; + + fn run(mut self, init: B, mut f: F) -> B + where + Self: Sized, + F: FnMut(B, Self::Item) -> B, + { + let mut accum = init; + while true { + thrust_macros::_invariant_with_context!( + #[thrust::_outer_context(trait Foo { type Item; })] + fn run(mut self: Self, init: B, mut f: F) -> B + where + // ← the partial-fix: add the host trait bound to + // the re-declared where clause. The macro copies + // it to the formula_fn where, so __ThrustSelf: Foo + // resolves the trait method calls. + Self: Sized + Foo, + F: FnMut(B, Self::Item) -> B; + |accum: B| thrust_models::exists( + |item: Self::Item| + Self::step(*self, item, *self) + && accum == init + ) + ); + break; + } + accum + } +} + +fn main() {} diff --git a/tests/ui/annot-error/invariant_context_trait_method.rs b/tests/ui/annot-error/invariant_context_trait_method.rs new file mode 100644 index 00000000..d4bea507 --- /dev/null +++ b/tests/ui/annot-error/invariant_context_trait_method.rs @@ -0,0 +1,27 @@ +// Reproduces: `#[thrust_macros::invariant_context]` attached to a *trait* +// method fails with E0401 ("can't use `Self` from outer item"). +// +// `invariant_context` is `ItemFn`-only (`thrust-macros/src/invariant_context.rs`). +// On a trait method it parses, but it never threads the trait-level `Self` +// through to the generated `formula_fn`, so the injected +// `_invariant_with_context!` macro ends up rewriting the closure body against +// the outer trait's `Self` (which is out of scope) and Rust rejects the use. + +#[thrust_macros::context] +trait Foo { + type Item; + + #[thrust_macros::invariant_context] + fn run(&mut self) + where + Self: Sized, + { + let mut x: i64 = 0; + while x < 1 { + thrust_macros::invariant!(|x: i64| x >= 0); + x += 1; + } + } +} + +fn main() {} diff --git a/tests/ui/annot-error/invariant_context_trait_method_workaround.rs b/tests/ui/annot-error/invariant_context_trait_method_workaround.rs new file mode 100644 index 00000000..5630319d --- /dev/null +++ b/tests/ui/annot-error/invariant_context_trait_method_workaround.rs @@ -0,0 +1,49 @@ +// Annotation-side workaround for `invariant_context_trait_method.rs`. +// +// The `#[thrust_macros::invariant_context]` attribute is `ItemFn`-only +// (its `expand` parses as `syn::ItemFn`), so attaching it to a trait +// method triggers E0401 because the trait's `Self` is out of scope for +// the generated `formula_fn`. The other annotation-side attempt — +// hand-rolling `thrust_macros::_invariant_with_context!` inside the +// loop body — runs into the same problem (the macro's `SelfRewriter` +// only kicks in when the closure body actually mentions `Self`; +// otherwise `Self: Model` constraints are produced against the outer +// `Self` and Rust rejects them). +// +// Workaround: drop the invariant on the trait method, and instead +// provide it on the concrete impl method, where `invariant_context` +// works (`ItemFn` parse target). The impl is the only place the +// invariant is meaningful anyway: the trait method's spec is +// independent of any concrete iterator type. + +#[thrust_macros::context] +trait Foo { + type Item; + + fn run(&mut self); +} + +struct Bar; + +impl thrust_models::Model for Bar { + type Ty = Bar; +} + +#[thrust_macros::context] +impl Foo for Bar { + type Item = i64; + + // `invariant_context` on an impl method is fine: the host is an + // `ItemFn` (`impl` method) and `Self` is the impl's self-type, + // not the trait's. + #[thrust_macros::invariant_context] + fn run(&mut self) { + let mut x: i64 = 0; + while x < 1 { + thrust_macros::invariant!(|x: i64| x >= 0); + x += 1; + } + } +} + +fn main() {} From 555106e51b6eb0fee9ccf1cac9c7228b812bd7e8 Mon Sep 17 00:00:00 2001 From: coeff-aij <175928954+coeff-aij@users.noreply.github.com> Date: Mon, 15 Jun 2026 16:37:30 +0900 Subject: [PATCH 056/142] fix: annotations on fold() --- tests/ui/pass/traits/fold.rs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/tests/ui/pass/traits/fold.rs b/tests/ui/pass/traits/fold.rs index 2265ec43..1097a08e 100644 --- a/tests/ui/pass/traits/fold.rs +++ b/tests/ui/pass/traits/fold.rs @@ -31,6 +31,7 @@ trait Iterator { acc.0[0] == init && Self::completed(it.0[l - 1]) && result == acc.0[l - 1] && + l > 0 && !( exists(|i| (0 <= i && i < l - 1) && @@ -66,6 +67,7 @@ trait Iterator { fn_.0[0] == f && acc.0[0] == init && accum == acc.0[l - 1] && + l > 0 && !( exists(|i: thrust_models::model::Int| (0 <= i && i < l - 1) && From 9d1db1978ea5f8cdf22ac34bad55bb30af62a2bc Mon Sep 17 00:00:00 2001 From: coeff-aij <175928954+coeff-aij@users.noreply.github.com> Date: Wed, 17 Jun 2026 17:26:18 +0900 Subject: [PATCH 057/142] fix: incorrect signature and duplication of ForallPred --- src/analyze/annot_fn.rs | 48 ++++++++++++++++++++++++++++++----------- src/chc.rs | 6 ++++-- src/chc/smtlib2.rs | 6 +----- src/refine.rs | 35 ++++++++++++++++++++++++++++-- 4 files changed, 74 insertions(+), 21 deletions(-) diff --git a/src/analyze/annot_fn.rs b/src/analyze/annot_fn.rs index 5375da32..53aeeb4f 100644 --- a/src/analyze/annot_fn.rs +++ b/src/analyze/annot_fn.rs @@ -396,14 +396,11 @@ impl<'a, 'tcx> AnnotFnTranslator<'a, 'tcx> { Some(rty::RefinedType::unrefined(ret_ty)) } - fn register_forall_pred(&self, type_params: Vec) -> chc::ForallPred { - let predicate = - refine::forall_pred(self.tcx, self.local_def_id.to_def_id(), type_params.clone()); + fn register_forall_pred(&self, forall_pred: chc::ForallPred) { self.analyzer .system .borrow_mut() - .register_forall_pred(predicate.clone()); - predicate + .register_forall_pred(forall_pred.clone()); } fn type_param_as_callable_sig(&self, param_ty: mir_ty::ParamTy) -> Option { @@ -434,9 +431,24 @@ impl<'a, 'tcx> AnnotFnTranslator<'a, 'tcx> { let value = chc::Term::var(rty::RefinedTypeVar::Value); let type_params = vec![self.type_builder.build(param_ty.to_ty(self.tcx)).to_sort()]; + let mut params_sort: Vec = params.iter().map(|rty| rty.ty.to_sort()).collect(); + let ret_sort = ret.ty.to_sort(); - let pre_pred = self.register_forall_pred(type_params.clone()); - let post_pred = self.register_forall_pred(type_params); + let pre_pred = refine::closure_pre_forall_pred( + self.tcx, + self.type_builder.owner_fn_id, + type_params.clone(), + params_sort.clone(), + ); + self.register_forall_pred(pre_pred.clone()); + params_sort.push(ret_sort); + let post_pred = refine::closure_post_forall_pred( + self.tcx, + self.type_builder.owner_fn_id, + type_params, + params_sort, + ); + self.register_forall_pred(post_pred.clone()); params[receiver].extend_refinement( chc::Atom::new(pre_pred.into(), vec![value.clone(), free(arg)]).into(), @@ -836,11 +848,23 @@ impl<'a, 'tcx> AnnotFnTranslator<'a, 'tcx> { .types() .map(|ty| self.type_builder.build(ty).to_sort()) .collect(); - let pred = refine::forall_pred(self.tcx, pred_def_id, type_params); - self.analyzer - .system - .borrow_mut() - .register_forall_pred(pred.clone()); + + let typeck_result = self.tcx.typeck(self.local_def_id); + let params = args + .iter() + .map(|expr| { + let ty = typeck_result.expr_ty(expr); + self.type_builder.build(ty).to_sort() + }) + .collect(); + + let pred = refine::trait_forall_pred( + self.tcx, + pred_def_id, + type_params, + params, + ); + self.register_forall_pred(pred.clone()); pred.into() } else { refine::user_defined_pred(self.tcx, pred_def_id).into() diff --git a/src/chc.rs b/src/chc.rs index 9861319a..757ce4a6 100644 --- a/src/chc.rs +++ b/src/chc.rs @@ -1051,6 +1051,7 @@ impl UserDefinedPred { pub struct ForallPred { inner: String, type_parameters: Vec, + params: Vec, } impl std::fmt::Display for ForallPred { @@ -1082,10 +1083,11 @@ where } impl ForallPred { - pub fn new(inner: String, args: Vec) -> Self { + pub fn new(inner: String, type_parameters: Vec, params: Vec) -> Self { Self { inner, - type_parameters: args, + type_parameters, + params, } } } diff --git a/src/chc/smtlib2.rs b/src/chc/smtlib2.rs index fdc2abe6..23633d66 100644 --- a/src/chc/smtlib2.rs +++ b/src/chc/smtlib2.rs @@ -600,11 +600,7 @@ pub struct ForallPredDef<'ctx, 'a> { impl<'ctx, 'a> std::fmt::Display for ForallPredDef<'ctx, 'a> { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - let params = self - .pred - .type_parameters - .iter() - .map(|sort| self.ctx.fmt_sort(sort)); + let params = self.pred.params.iter().map(|sort| self.ctx.fmt_sort(sort)); let params = List::closed(params); write!( f, diff --git a/src/refine.rs b/src/refine.rs index 5ecd82c9..afa14054 100644 --- a/src/refine.rs +++ b/src/refine.rs @@ -41,6 +41,37 @@ pub fn user_defined_pred(tcx: mir_ty::TyCtxt<'_>, did: DefId) -> UserDefinedPred UserDefinedPred::new(stable_def_id_symbol(tcx, did, "p")) } -pub fn forall_pred(tcx: mir_ty::TyCtxt<'_>, did: DefId, args: Vec) -> ForallPred { - ForallPred::new(stable_def_id_symbol(tcx, did, "q"), args) +pub fn trait_forall_pred( + tcx: mir_ty::TyCtxt<'_>, + did: DefId, + type_parameters: Vec, + params: Vec, +) -> ForallPred { + ForallPred::new(stable_def_id_symbol(tcx, did, "q"), type_parameters, params) +} + +pub fn closure_pre_forall_pred( + tcx: mir_ty::TyCtxt<'_>, + did: DefId, + type_parameters: Vec, + params: Vec, +) -> ForallPred { + ForallPred::new( + stable_def_id_symbol(tcx, did, "q_pre"), + type_parameters, + params, + ) +} + +pub fn closure_post_forall_pred( + tcx: mir_ty::TyCtxt<'_>, + did: DefId, + type_parameters: Vec, + params: Vec, +) -> ForallPred { + ForallPred::new( + stable_def_id_symbol(tcx, did, "q_post"), + type_parameters, + params, + ) } From 77a562e2b1e207334bdca334ede33bcbf8bbb9f1 Mon Sep 17 00:00:00 2001 From: coeff-aij <175928954+coeff-aij@users.noreply.github.com> Date: Sat, 20 Jun 2026 17:54:03 +0900 Subject: [PATCH 058/142] fix: propagete owner_fn_id to AnnotFnTranslator --- src/analyze.rs | 5 +++-- src/analyze/annot_fn.rs | 21 +++++++-------------- src/analyze/basic_block.rs | 4 ++-- src/analyze/local_def.rs | 7 ++++++- src/refine/template.rs | 8 ++++++-- 5 files changed, 24 insertions(+), 21 deletions(-) diff --git a/src/analyze.rs b/src/analyze.rs index e0133112..de3457aa 100644 --- a/src/analyze.rs +++ b/src/analyze.rs @@ -497,8 +497,9 @@ impl<'tcx> Analyzer<'tcx> { return Some(formula_fn.clone()); } - let translator = annot_fn::AnnotFnTranslator::new(self, local_def_id, generic_args) - .with_def_id_cache(self.def_ids(), owner_fn_id); + let translator = + annot_fn::AnnotFnTranslator::new(self, local_def_id, generic_args, owner_fn_id) + .with_def_id_cache(self.def_ids()); let formula_fn = translator.to_formula_fn(); deferred_formula_fn_cache .borrow_mut() diff --git a/src/analyze/annot_fn.rs b/src/analyze/annot_fn.rs index 861f80a3..acedb2c3 100644 --- a/src/analyze/annot_fn.rs +++ b/src/analyze/annot_fn.rs @@ -181,6 +181,7 @@ impl<'a, 'tcx> AnnotFnTranslator<'a, 'tcx> { analyzer: &'a analyze::Analyzer<'tcx>, local_def_id: LocalDefId, generic_args: mir_ty::GenericArgsRef<'tcx>, + owner_fn_id: DefId, ) -> Self { let tcx = analyzer.tcx(); let body = tcx.hir_body_owned_by(local_def_id); @@ -189,7 +190,7 @@ impl<'a, 'tcx> AnnotFnTranslator<'a, 'tcx> { let type_builder = TypeBuilder::new( tcx, def_ids.clone(), - local_def_id.to_def_id(), + owner_fn_id, analyzer.type_params.clone(), analyzer.closure_type_params.clone(), analyzer.system.clone(), @@ -209,16 +210,8 @@ impl<'a, 'tcx> AnnotFnTranslator<'a, 'tcx> { translator } - pub fn with_def_id_cache(mut self, def_ids: DefIdCache<'tcx>, owner_fn_id: DefId) -> Self { + pub fn with_def_id_cache(mut self, def_ids: DefIdCache<'tcx>) -> Self { self.def_ids = def_ids; - self.type_builder = TypeBuilder::new( - self.tcx, - self.def_ids.clone(), - owner_fn_id, - self.analyzer.type_params.clone(), - self.analyzer.closure_type_params.clone(), - self.analyzer.system.clone(), - ); self } @@ -393,7 +386,7 @@ impl<'a, 'tcx> AnnotFnTranslator<'a, 'tcx> { ); if let Some(closure_fun_ty) = closure_fun_ty.clone() { self.type_builder.register_closure_type_param( - analyze::TypeParam::GenericType(self.type_builder.owner_fn_id, ty.index), + analyze::TypeParam::GenericType(self.type_builder.owner_fn_id(), ty.index), closure_fun_ty, ); }; @@ -493,7 +486,7 @@ impl<'a, 'tcx> AnnotFnTranslator<'a, 'tcx> { let pre_pred = refine::closure_pre_forall_pred( self.tcx, - self.type_builder.owner_fn_id, + self.type_builder.owner_fn_id(), type_params.clone(), params_sort.clone(), ); @@ -501,7 +494,7 @@ impl<'a, 'tcx> AnnotFnTranslator<'a, 'tcx> { params_sort.push(ret_sort); let post_pred = refine::closure_post_forall_pred( self.tcx, - self.type_builder.owner_fn_id, + self.type_builder.owner_fn_id(), type_params, params_sort, ); @@ -933,7 +926,7 @@ impl<'a, 'tcx> AnnotFnTranslator<'a, 'tcx> { }; let pred = if is_unresolved_args { - tracing::debug!(?self.local_def_id, ?generic_args, ?self.type_builder.owner_fn_id); + tracing::debug!(?self.local_def_id, ?generic_args, "owner_fn_id={:?}", self.type_builder.owner_fn_id()); let type_params = generic_args .types() .map(|ty| self.type_builder.build(ty).to_sort()) diff --git a/src/analyze/basic_block.rs b/src/analyze/basic_block.rs index 6fd6e0b5..60ec2b7b 100644 --- a/src/analyze/basic_block.rs +++ b/src/analyze/basic_block.rs @@ -856,7 +856,7 @@ impl<'tcx, 'ctx> Analyzer<'tcx, 'ctx> { ResolvedCallable::Closure(*closure_def_id, parent_args) } mir_ty::TyKind::Param(ty) => ResolvedCallable::Generic(TypeParam::GenericType( - self.type_builder.owner_fn_id, + self.type_builder.owner_fn_id(), ty.index, )), kind => { @@ -880,7 +880,7 @@ impl<'tcx, 'ctx> Analyzer<'tcx, 'ctx> { def_id: DefId, args: mir_ty::GenericArgsRef<'tcx>, ) -> rty::Type { - let caller_def_id = self.type_builder.owner_fn_id; + let caller_def_id = self.type_builder.owner_fn_id(); if let Some(def_ty) = self.ctx.def_ty_with_args(def_id, args, caller_def_id) { return def_ty.ty; } diff --git a/src/analyze/local_def.rs b/src/analyze/local_def.rs index bbd052b9..489f2c44 100644 --- a/src/analyze/local_def.rs +++ b/src/analyze/local_def.rs @@ -1177,7 +1177,7 @@ impl<'tcx, 'ctx> Analyzer<'tcx, 'ctx> { .clone(); let drop_points = self.drop_points[&bb].clone(); self.ctx - .basic_block_analyzer(self.local_def_id, bb, self.body.source.def_id()) + .basic_block_analyzer(self.local_def_id, bb, self.owner_fn_id) .body(self.body.clone()) .drop_points(drop_points) .run(&rty, expected_fn_ty); @@ -1329,6 +1329,11 @@ impl<'tcx, 'ctx> Analyzer<'tcx, 'ctx> { } pub fn owner_fn_id(&mut self, owner_fn_id: DefId) -> &mut Self { + tracing::debug!( + "change owner_fn_id from {:?} to {:?}.", + self.owner_fn_id, + owner_fn_id + ); self.owner_fn_id = owner_fn_id; self.type_builder = self.ctx.type_builder(self.ctx.def_ids(), owner_fn_id); self diff --git a/src/refine/template.rs b/src/refine/template.rs index 7cb7e5d9..4062ed05 100644 --- a/src/refine/template.rs +++ b/src/refine/template.rs @@ -73,7 +73,7 @@ where pub struct TypeBuilder<'tcx> { tcx: mir_ty::TyCtxt<'tcx>, def_ids: DefIdCache<'tcx>, - pub owner_fn_id: DefId, + owner_fn_id: DefId, typing_env: mir_ty::TypingEnv<'tcx>, /// Maps index in [`mir_ty::ParamTy`] to [`rty::TypeParamIdx`]. /// These indices may differ because we skip lifetime parameters and they always need to be @@ -107,7 +107,7 @@ impl<'tcx> TypeBuilder<'tcx> { } } - tracing::debug!("TypeBuilder is created for {owner_fn_id:?}."); + tracing::debug!("TypeBuilder is created for {owner_fn_id:?} with param_idx_mapping {param_idx_mapping:#?}."); let typing_env = mir_ty::TypingEnv::post_analysis(tcx, owner_fn_id); Self { tcx, @@ -121,6 +121,10 @@ impl<'tcx> TypeBuilder<'tcx> { } } + pub fn owner_fn_id(&self) -> DefId { + self.owner_fn_id + } + fn translate_param_type(&self, ty: &mir_ty::ParamTy) -> rty::Type { let param_local_idx = *self .param_idx_mapping From 9027871b6d2fcf3e9b470bcad6645b455259fc6d Mon Sep 17 00:00:00 2001 From: coeff-aij <175928954+coeff-aij@users.noreply.github.com> Date: Sat, 20 Jun 2026 18:03:18 +0900 Subject: [PATCH 059/142] fix: runtime error with borrowing RefCell --- src/refine/template.rs | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/refine/template.rs b/src/refine/template.rs index 4062ed05..763bd39e 100644 --- a/src/refine/template.rs +++ b/src/refine/template.rs @@ -148,6 +148,7 @@ impl<'tcx> TypeBuilder<'tcx> { } fn translate_alias_type(&self, ty: &mir_ty::AliasTy<'tcx>) -> rty::Type { + let args: Vec> = ty.args.types().map(|t| self.build(t)).collect(); let mut type_params = self.type_params.borrow_mut(); let index = type_params .entry(TypeParam::AssocType(ty.def_id, ty.args)) @@ -157,8 +158,6 @@ impl<'tcx> TypeBuilder<'tcx> { idx }); - let args: Vec> = ty.args.types().map(|t| self.build(t)).collect(); - rty::AliasType::new(*index, args).into() } From 164e7711746885a537b32dc9e984ea7d166d0776 Mon Sep 17 00:00:00 2001 From: coeff-aij <175928954+coeff-aij@users.noreply.github.com> Date: Sat, 20 Jun 2026 18:06:08 +0900 Subject: [PATCH 060/142] add: translate AliasTy with TemplateTypeBuilder --- src/refine/template.rs | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/src/refine/template.rs b/src/refine/template.rs index 763bd39e..ae4be9d8 100644 --- a/src/refine/template.rs +++ b/src/refine/template.rs @@ -571,6 +571,19 @@ where unimplemented!("unsupported ADT: {:?}", ty); } } + mir_ty::TyKind::Alias(mir_ty::AliasTyKind::Projection, ty) => { + if let Some(model_ty_def_id) = self.inner.def_ids.model_ty() { + let arg_ty = ty.args.type_at(0); + + if ty.def_id == model_ty_def_id + && matches!(arg_ty.kind(), mir_ty::TyKind::Param(_)) + { + return self.build(arg_ty); + } + } + + self.inner.translate_alias_type(ty).vacuous() + } kind => unimplemented!("ty: {:?}", kind), } } From 3bafc7808262a0cfcb96ef16b098f81cd54d7983 Mon Sep 17 00:00:00 2001 From: coeff-aij <175928954+coeff-aij@users.noreply.github.com> Date: Sat, 20 Jun 2026 23:32:16 +0900 Subject: [PATCH 061/142] change: use deferred type for generic functions whose annotations and mir body aren't available --- src/analyze/crate_.rs | 18 ++++++++++++------ 1 file changed, 12 insertions(+), 6 deletions(-) diff --git a/src/analyze/crate_.rs b/src/analyze/crate_.rs index 2188b099..e1e66b30 100644 --- a/src/analyze/crate_.rs +++ b/src/analyze/crate_.rs @@ -115,12 +115,18 @@ impl<'tcx, 'ctx> Analyzer<'tcx, 'ctx> { let owner_fn_id = analyzer.owner_fn_id; use mir_ty::TypeVisitableExt as _; if sig.has_param() { - let expected = self - .tcx - .is_mir_available(owner_fn_id) - .then(|| analyzer.expected_ty()); - self.ctx - .register_generic_def(owner_fn_id, local_def_id, expected); + if owner_fn_id.as_local().is_none_or(|def_id| { + self.skip_analysis.contains(&def_id) || !self.tcx.is_mir_available(def_id) + }) { + self.ctx + .register_deferred_def_without_analysis(owner_fn_id, local_def_id); + } else if analyzer.is_fully_annotated() { + let expected = analyzer.expected_ty(); + self.ctx + .register_generic_def(owner_fn_id, local_def_id, Some(expected)); + } else { + self.ctx.register_deferred_def(owner_fn_id, local_def_id); + } } else { let expected = analyzer.expected_ty(); self.ctx.register_def(owner_fn_id, expected); From 481829f66432639856fdcc6995c6783260093ae7 Mon Sep 17 00:00:00 2001 From: coeff-aij <175928954+coeff-aij@users.noreply.github.com> Date: Sun, 21 Jun 2026 02:47:04 +0900 Subject: [PATCH 062/142] fix: wrong abi for closure --- src/analyze/annot_fn.rs | 45 ++++++++++++++++++++++++++++++++--------- 1 file changed, 35 insertions(+), 10 deletions(-) diff --git a/src/analyze/annot_fn.rs b/src/analyze/annot_fn.rs index acedb2c3..ce9dcb7a 100644 --- a/src/analyze/annot_fn.rs +++ b/src/analyze/annot_fn.rs @@ -419,11 +419,18 @@ impl<'a, 'tcx> AnnotFnTranslator<'a, 'tcx> { FnOnce => receiver_type, }; - let other_params = self.type_builder.build(trait_ref.args.type_at(1)); - let params = [receiver_type, other_params] - .into_iter() - .map(|ty| rty::RefinedType::unrefined(ty.vacuous())) + let mir_ty::Tuple(other_params) = trait_ref.args.type_at(1).kind() else { + panic!() + }; + + let other_params = other_params + .iter() + .map(|ty| self.type_builder.build(ty).vacuous()); + let params = std::iter::once(receiver_type.vacuous()) + .chain(other_params) + .map(rty::RefinedType::unrefined) .collect(); + tracing::debug!("found the signature for closure trait: {params:#?}"); Some(params) } @@ -474,12 +481,17 @@ impl<'a, 'tcx> AnnotFnTranslator<'a, 'tcx> { self.closure_trait_ret(param_ty, clause.as_projection_clause()?.skip_binder()) })?; - let receiver = rty::FunctionParamIdx::from_usize(0); - let arg = rty::FunctionParamIdx::from_usize(1); - let free = |idx| chc::Term::var(rty::RefinedTypeVar::Free(idx)); let value = chc::Term::var(rty::RefinedTypeVar::Value); + let receiver = rty::FunctionParamIdx::from_usize(0); + let args: Vec<_> = params + .iter() + .enumerate() + .skip(1) + .map(|(idx, _)| free(rty::FunctionParamIdx::from_usize(idx))) + .collect(); + let type_params = vec![self.type_builder.build(param_ty.to_ty(self.tcx)).to_sort()]; let mut params_sort: Vec = params.iter().map(|rty| rty.ty.to_sort()).collect(); let ret_sort = ret.ty.to_sort(); @@ -501,14 +513,27 @@ impl<'a, 'tcx> AnnotFnTranslator<'a, 'tcx> { self.register_forall_pred(post_pred.clone()); params[receiver].extend_refinement( - chc::Atom::new(pre_pred.into(), vec![value.clone(), free(arg)]).into(), + chc::Atom::new( + pre_pred.into(), + [vec![value.clone()], args.clone()].concat(), + ) + .into(), ); ret.extend_refinement( - chc::Atom::new(post_pred.into(), vec![free(receiver), free(arg), value]).into(), + chc::Atom::new( + post_pred.into(), + [vec![free(receiver)], args, vec![value.clone()]].concat(), + ) + .into(), ); + let ret = Box::new(ret); - Some(rty::FunctionType::new(params, ret)) + Some(rty::FunctionType { + params, + ret, + abi: rty::FunctionAbi::RustCall, + }) } /// Extracts the logical argument terms passed to `closure_precondition`/ From 675b0e7c6f938a2dfb59f3a86c013fc5cb6d182c Mon Sep 17 00:00:00 2001 From: coeff-aij <175928954+coeff-aij@users.noreply.github.com> Date: Sun, 21 Jun 2026 21:42:43 +0900 Subject: [PATCH 063/142] fix: annotations on fold() --- tests/ui/pass/traits/fold.rs | 80 ++++++++++++++++-------------------- 1 file changed, 36 insertions(+), 44 deletions(-) diff --git a/tests/ui/pass/traits/fold.rs b/tests/ui/pass/traits/fold.rs index 1097a08e..7935394e 100644 --- a/tests/ui/pass/traits/fold.rs +++ b/tests/ui/pass/traits/fold.rs @@ -2,7 +2,7 @@ //@compile-flags: -C debug-assertions=off //@rustc-env: THRUST_SOLVER=tests/thrust-pcsat-wrapper THRUST_SOLVER_TIMEOUT_SECS=60 -use thrust_models::exists; +use thrust_models::{Model, exists, forall, model::Mut}; #[thrust_macros::context] trait Iterator { @@ -23,28 +23,24 @@ trait Iterator { #[thrust_macros::invariant_context] #[thrust_macros::requires(true)] #[thrust_macros::ensures( - exists(|it: thrust_models::model::Vec| - exists(|fn_: thrust_models::model::Vec| - exists(|acc| + exists(|it: thrust_models::model::Array::Ty>| + exists(|fn_: thrust_models::model::Array>| + exists(|acc: thrust_models::model::Array::Ty>| exists(|l: thrust_models::model::Int| - it.0[0] == self && - acc.0[0] == init && - Self::completed(it.0[l - 1]) && - result == acc.0[l - 1] && - l > 0 && - !( - exists(|i| - (0 <= i && i < l - 1) && - !( - exists(|item| - !Self::completed(it.0[i]) && - Self::step(it.0[i], item, it.0[i + 1]) && - thrust_macros::pre!(f(acc.0[i], item)) && - thrust_macros::post!( - f(acc.0[i], item), - acc.0[i + 1] - ) - ) + it[0] == self && + fn_[0] == f && + acc[0] == init && + Self::completed(it[l - 1]) && + result == acc[l - 1] && + forall(|i: thrust_models::model::Int| + 0 <= i && i < l - 1 ==> + exists(|item| + !Self::completed(it[i]) && + Self::step(it[i], item, it[i + 1]) && + thrust_macros::pre!(Mut::new(fn_[i], fn_[i+1])(acc[i], item)) && + thrust_macros::post!( + Mut::new(fn_[i], fn_[i+1])(acc[i], item), + acc[i + 1] ) ) ) @@ -58,29 +54,24 @@ trait Iterator { let mut accum = init; while let Some(x) = self.next() { thrust_macros::invariant!( - |accum: B| - exists(|it: thrust_models::model::Vec| - exists(|fn_: thrust_models::model::Vec| - exists(|acc| + |accum: B, init: thrust_models::FnParam, f: F, self: Self| + exists(|it: thrust_models::model::Array::Ty>| + exists(|fn_: thrust_models::model::Array>| + exists(|acc: thrust_models::model::Array::Ty>| exists(|l: thrust_models::model::Int| - it.0[0] == self && - fn_.0[0] == f && - acc.0[0] == init && - accum == acc.0[l - 1] && - l > 0 && - !( - exists(|i: thrust_models::model::Int| - (0 <= i && i < l - 1) && - !( - exists(|item: Self::Item| - !Self::completed(it.0[i]) && - Self::step(it.0[i], item, it.0[i + 1]) && - thrust_macros::pre!(f(acc.0[i], item)) && - thrust_macros::post!( - f(acc.0[i], item), - acc.0[i + 1] - ) - ) + it[0] == self && + fn_[0] == f && + acc[0] == init.at_entry() && + accum == acc[l - 1] && + forall(|i: thrust_models::model::Int| + 0 <= i && i < l - 1 ==> + exists(|item: ::Ty| + !Self::completed(it[i]) && + Self::step(it[i], item, it[i + 1]) && + thrust_macros::pre!(Mut::new(fn_[i], fn_[i+1])(acc[i], item)) && + thrust_macros::post!( + Mut::new(fn_[i], fn_[i+1])(acc[i], item), + acc[i + 1] ) ) ) @@ -92,6 +83,7 @@ trait Iterator { } } +#[derive(PartialEq)] struct Range { start: i64, end: i64, From a0ce8fbd70a3d98f18453d6a149f0c57c0955b47 Mon Sep 17 00:00:00 2001 From: coeff-aij <175928954+coeff-aij@users.noreply.github.com> Date: Sun, 21 Jun 2026 22:08:55 +0900 Subject: [PATCH 064/142] add: expand projection into T during loop invariant translation(ad-hoc) --- src/analyze/local_def.rs | 32 +++++++++++++++++++++++++++++++- 1 file changed, 31 insertions(+), 1 deletion(-) diff --git a/src/analyze/local_def.rs b/src/analyze/local_def.rs index 489f2c44..51678f00 100644 --- a/src/analyze/local_def.rs +++ b/src/analyze/local_def.rs @@ -963,18 +963,24 @@ impl<'tcx, 'ctx> Analyzer<'tcx, 'ctx> { fn local_of_name_in_bb(&self, name: rustc_span::Symbol, bty: &BasicBlockType) -> Option { let mut found: Option = None; for vdi in &self.body.var_debug_info { + tracing::debug!("comparing {name:?} with {vdi:?}..."); if vdi.name != name { + tracing::debug!("different name, skip."); continue; } let mir::VarDebugInfoContents::Place(place) = vdi.value else { + tracing::debug!("place, skip."); continue; }; if !place.projection.is_empty() { + tracing::debug!("empty projection, skip."); continue; } if bty.param_of_local(place.local).is_none() { + tracing::debug!("not param of local, skip."); continue; } + tracing::debug!("found."); match found { None => found = Some(place.local), Some(prev) if prev == place.local => {} @@ -991,19 +997,25 @@ impl<'tcx, 'ctx> Analyzer<'tcx, 'ctx> { fn function_param_local_of_name(&self, name: rustc_span::Symbol) -> Option { let mut found: Option = None; for vdi in &self.body.var_debug_info { + tracing::debug!("comparing {name:?} with {vdi:?}..."); if vdi.name != name { + tracing::debug!("different name, skip."); continue; } let mir::VarDebugInfoContents::Place(place) = vdi.value else { + tracing::debug!("place, skip."); continue; }; if !place.projection.is_empty() { + tracing::debug!("empty projection, skip."); continue; } let local = place.local; if local.index() == 0 || local.index() > self.body.arg_count { + tracing::debug!("not param of local, skip."); continue; } + tracing::debug!("found."); match found { None => found = Some(local), Some(prev) if prev == local => {} @@ -1013,6 +1025,19 @@ impl<'tcx, 'ctx> Analyzer<'tcx, 'ctx> { found } + fn expand_model_projection(&self, ty: mir_ty::Ty<'tcx>) -> mir_ty::Ty<'tcx> { + if let mir_ty::Alias(mir_ty::AliasTyKind::Projection, ty) = ty.kind() { + if let Some(model_ty_def_id) = self.ctx.def_ids.model_ty() { + let arg_ty = ty.args.type_at(0); + + if ty.def_id == model_ty_def_id { + return arg_ty; + } + } + } + ty + } + /// Translates a user-provided loop invariant (a formula function over named /// live variables) into a precondition refinement over `bty`'s parameters. /// Each formula parameter names a live variable at the loop header and is @@ -1044,6 +1069,9 @@ impl<'tcx, 'ctx> Analyzer<'tcx, 'ctx> { .unwrap_or(*input_ty) }; + let input_ty = self.expand_model_projection(input_ty); + tracing::debug!(?ident_opt, ?input_ty, "resolving"); + // The synthetic `__thrust_self` parameter (emitted when an invariant refers to the receiver // `self`) maps to the loop-carried receiver, which appears as `self` in debug info. let name = if name.as_str() == "__thrust_self" { @@ -1051,11 +1079,12 @@ impl<'tcx, 'ctx> Analyzer<'tcx, 'ctx> { } else { name }; - + tracing::debug!("{:?}", input_ty.ty_adt_def()); if input_ty .ty_adt_def() .is_some_and(|def| Some(def.did()) == self.ctx.def_ids().fn_param_wrapper()) { + tracing::debug!("fn_param_local: {input_ty:?}"); let local = self.function_param_local_of_name(name).unwrap_or_else(|| { self.tcx.dcx().fatal(format!( "loop invariant refers to `{name}` via FnParam, but it is not a function parameter" @@ -1064,6 +1093,7 @@ impl<'tcx, 'ctx> Analyzer<'tcx, 'ctx> { let param_idx = crate::analyze::function_param_of_local(local); mapping.push(bty.param_of_outer_fn_param(param_idx).unwrap()); } else { + tracing::debug!("local: {input_ty:?}"); let local = self.local_of_name_in_bb(name, bty).unwrap_or_else(|| { self.tcx.dcx().fatal(format!( "loop invariant refers to `{name}`, which is not a live variable at the loop header" From 5dcba45a933b08088b59fb9841d4fad2d6c692c1 Mon Sep 17 00:00:00 2001 From: coeff-aij <175928954+coeff-aij@users.noreply.github.com> Date: Sun, 21 Jun 2026 23:51:51 +0900 Subject: [PATCH 065/142] change: expand ::Ty into AliasTy --- src/refine/template.rs | 16 ++++++++++++++-- 1 file changed, 14 insertions(+), 2 deletions(-) diff --git a/src/refine/template.rs b/src/refine/template.rs index ae4be9d8..e61463bb 100644 --- a/src/refine/template.rs +++ b/src/refine/template.rs @@ -363,8 +363,14 @@ impl<'tcx> TypeBuilder<'tcx> { let arg_ty = ty.args.type_at(0); if ty.def_id == model_ty_def_id - && matches!(arg_ty.kind(), mir_ty::TyKind::Param(_)) + && (matches!( + arg_ty.kind(), + mir_ty::TyKind::Param(_) | mir_ty::TyKind::Alias(..) + )) { + tracing::debug!( + "expanding projection to thrust_models::Model::Ty for {arg_ty:?}." + ); return self.build(arg_ty); } } @@ -576,8 +582,14 @@ where let arg_ty = ty.args.type_at(0); if ty.def_id == model_ty_def_id - && matches!(arg_ty.kind(), mir_ty::TyKind::Param(_)) + && (matches!( + arg_ty.kind(), + mir_ty::TyKind::Param(_) | mir_ty::TyKind::Alias(..) + )) { + tracing::debug!( + "expanding projection to thrust_models::Model::Ty for {arg_ty:?}." + ); return self.build(arg_ty); } } From 9d65be6c3386ca05ff5401f617d15aeabe3fbb00 Mon Sep 17 00:00:00 2001 From: coeff-aij <175928954+coeff-aij@users.noreply.github.com> Date: Mon, 22 Jun 2026 00:53:20 +0900 Subject: [PATCH 066/142] fix: treat ParamTy(__ThrustSelf) as ParamTy(Self) (ad-hoc) --- src/analyze.rs | 10 +++++----- src/analyze/basic_block.rs | 2 +- src/chc.rs | 8 ++++---- src/refine/template.rs | 38 +++++++++++++++++++++++++++++--------- src/rty.rs | 24 ++++++++++++------------ 5 files changed, 51 insertions(+), 31 deletions(-) diff --git a/src/analyze.rs b/src/analyze.rs index de3457aa..5c9cf9bc 100644 --- a/src/analyze.rs +++ b/src/analyze.rs @@ -211,12 +211,12 @@ impl refine::EnumDefProvider for Rc> { } pub type Env = refine::Env>>; -pub type TypeParamMap<'tcx> = HashMap, ForallSortIdx>; +pub type TypeParamMap<'tcx> = HashMap; #[derive(Eq, PartialEq, Hash, Debug, Clone)] -pub enum TypeParam<'tcx> { +pub enum TypeParam { GenericType(DefId, u32), - AssocType(DefId, mir_ty::GenericArgsRef<'tcx>), + AssocType(DefId, Vec>), } #[derive(Debug, Clone)] @@ -247,7 +247,7 @@ pub struct Analyzer<'tcx> { enum_defs: Rc>, type_params: Rc>>, - closure_type_params: Rc, rty::FunctionType>>>, + closure_type_params: Rc>>, } impl<'tcx> crate::refine::TemplateRegistry for Analyzer<'tcx> { @@ -441,7 +441,7 @@ impl<'tcx> Analyzer<'tcx> { ); } - pub fn get_closure_type(&self, type_param: TypeParam<'tcx>) -> Option { + pub fn get_closure_type(&self, type_param: TypeParam) -> Option { self.closure_type_params.borrow().get(&type_param).cloned() } diff --git a/src/analyze/basic_block.rs b/src/analyze/basic_block.rs index 60ec2b7b..1f8aeed3 100644 --- a/src/analyze/basic_block.rs +++ b/src/analyze/basic_block.rs @@ -133,7 +133,7 @@ impl PrecondCapture { enum ResolvedCallable<'tcx> { Closure(DefId, mir_ty::GenericArgsRef<'tcx>), - Generic(TypeParam<'tcx>), + Generic(TypeParam), } pub struct Analyzer<'tcx, 'ctx> { diff --git a/src/chc.rs b/src/chc.rs index fa2e5ed2..eb610ab1 100644 --- a/src/chc.rs +++ b/src/chc.rs @@ -488,7 +488,7 @@ impl Function { } /// A logical term. -#[derive(Debug, Clone)] +#[derive(Debug, Clone, PartialEq, Eq, Hash)] pub enum Term { Null, Var(V), @@ -1226,7 +1226,7 @@ impl TryFrom for ForallPred { } /// An atom is a predicate applied to a list of terms. -#[derive(Debug, Clone)] +#[derive(Debug, Clone, PartialEq, Eq, Hash)] pub struct Atom { /// With `guard`, this represents `guard => pred(args)`. /// @@ -1370,7 +1370,7 @@ impl Atom { /// While it allows arbitrary [`Atom`] in its `Atom` variant, we only expect atoms with known /// predicates (i.e., predicates other than `Pred::Var`) to appear in formulas. It is our TODO to /// enforce this restriction statically. Also see the definition of [`Body`]. -#[derive(Debug, Clone)] +#[derive(Debug, Clone, PartialEq, Eq, Hash)] pub enum Formula { Atom(Atom), Not(Box>), @@ -1692,7 +1692,7 @@ impl Formula { } /// The body part of a clause, consisting of atoms and a formula. -#[derive(Debug, Clone)] +#[derive(Debug, Clone, PartialEq, Eq, Hash)] pub struct Body { pub atoms: Vec>, /// NOTE: This doesn't contain predicate variables. Also see [`Formula`]. diff --git a/src/refine/template.rs b/src/refine/template.rs index e61463bb..a79a40e6 100644 --- a/src/refine/template.rs +++ b/src/refine/template.rs @@ -81,7 +81,7 @@ pub struct TypeBuilder<'tcx> { /// See [`rty::TypeParamIdx`] for more details. param_idx_mapping: HashMap, type_params: Rc>>, - closure_type_params: Rc, rty::FunctionType>>>, + closure_type_params: Rc>>, system: Rc>, } @@ -91,7 +91,7 @@ impl<'tcx> TypeBuilder<'tcx> { def_ids: DefIdCache<'tcx>, owner_fn_id: DefId, type_params: Rc>>, - closure_type_params: Rc, rty::FunctionType>>>, + closure_type_params: Rc>>, system: Rc>, ) -> Self { let generics = tcx.generics_of(owner_fn_id); @@ -126,11 +126,34 @@ impl<'tcx> TypeBuilder<'tcx> { } fn translate_param_type(&self, ty: &mir_ty::ParamTy) -> rty::Type { + // FIXME: + // `__ThrustSelf` is currently treated as a distinct `ParamTy` from `Self`, + // which can lead to cache/key mismatches (e.g. in `TypeParamMap`). + // + // We currently normalize it here as a workaround, but this should be done + // earlier during type translation/analysis so that all internal + // representations consistently use the canonical `Self` parameter. + if ty.name.as_str() == "__ThrustSelf" { + let parent_def_id = self.tcx.parent(self.owner_fn_id); + let self_ty_def = self + .tcx + .generics_of(parent_def_id) + .own_params + .iter() + .find(|ty| ty.name.as_str() == "Self") + .expect("Type parameter `Self` is not found."); + + let self_ty = mir_ty::ParamTy::new(self_ty_def.index, self_ty_def.name); + tracing::debug!("replace {ty:?} with {self_ty:?}."); + return self.translate_param_type(&self_ty); + } let param_local_idx = *self .param_idx_mapping .get(&ty.index) .expect("unknown type param idx"); + tracing::debug!("translating ParamTy {ty:?}..."); + let mut type_params = self.type_params.borrow_mut(); let forall_sort_idx = type_params .entry(TypeParam::GenericType(self.owner_fn_id, ty.index)) @@ -150,22 +173,19 @@ impl<'tcx> TypeBuilder<'tcx> { fn translate_alias_type(&self, ty: &mir_ty::AliasTy<'tcx>) -> rty::Type { let args: Vec> = ty.args.types().map(|t| self.build(t)).collect(); let mut type_params = self.type_params.borrow_mut(); + tracing::debug!(?type_params); let index = type_params - .entry(TypeParam::AssocType(ty.def_id, ty.args)) + .entry(TypeParam::AssocType(ty.def_id, args.clone())) .or_insert_with(|| { let idx = self.system.borrow_mut().new_forall_sort(); - tracing::debug!("issue the new ForallSortIdx {} for AliasTy {:?}.", idx, ty,); + tracing::debug!("issue the new ForallSortIdx {} for AliasTy {:?} with (def_id = {:?}, args = {:?}).", idx, ty, ty.def_id, args); idx }); rty::AliasType::new(*index, args).into() } - pub fn register_closure_type_param( - &self, - type_param: TypeParam<'tcx>, - fun_type: rty::FunctionType, - ) { + pub fn register_closure_type_param(&self, type_param: TypeParam, fun_type: rty::FunctionType) { tracing::info!(?type_param, ?fun_type, "register_closure_type_param"); self.closure_type_params .borrow_mut() diff --git a/src/rty.rs b/src/rty.rs index 2a270050..85de6e42 100644 --- a/src/rty.rs +++ b/src/rty.rs @@ -192,7 +192,7 @@ impl FunctionAbi { /// In Thrust, function types are closed. Because of that, function types, thus its parameters and /// return type only refer to the parameters of the function itself using [`FunctionParamIdx`] and /// do not accept other type of variables from the environment. -#[derive(Debug, Clone)] +#[derive(Debug, Clone, PartialEq, Eq, Hash)] pub struct FunctionType { pub params: IndexVec>, pub ret: Box>, @@ -397,7 +397,7 @@ where } /// The kind of a reference, which is either mutable or immutable. -#[derive(Debug, Clone, Copy, PartialEq, Eq)] +#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] pub enum RefKind { Mut, Immut, @@ -422,7 +422,7 @@ where } /// The kind of a pointer, which is either a reference or an owned pointer. -#[derive(Debug, Clone, Copy, PartialEq, Eq)] +#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] pub enum PointerKind { Ref(RefKind), Own, @@ -462,7 +462,7 @@ impl PointerKind { } /// A pointer type. -#[derive(Debug, Clone)] +#[derive(Debug, Clone, PartialEq, Eq, Hash)] pub struct PointerType { pub kind: PointerKind, pub elem: Box>, @@ -575,7 +575,7 @@ impl PointerType { /// Note that the current implementation uses tuples to represent structs. See /// implementation in `crate::refine::template` module for details. /// It is our TODO to improve the struct representation. -#[derive(Debug, Clone)] +#[derive(Debug, Clone, PartialEq, Eq, Hash)] pub struct TupleType { pub elems: Vec>, } @@ -699,7 +699,7 @@ impl EnumDatatypeDef { /// An enum type. /// /// An enum type includes its type arguments and the argument types can refer to outer variables `T`. -#[derive(Debug, Clone)] +#[derive(Debug, Clone, PartialEq, Eq, Hash)] pub struct EnumType { pub symbol: chc::DatatypeSymbol, pub args: IndexVec>, @@ -801,7 +801,7 @@ impl EnumType { } /// A type parameter. -#[derive(Debug, Clone)] +#[derive(Debug, Clone, PartialEq, Eq, Hash)] pub struct ParamType { type_param_idx: TypeParamIdx, forall_sort_idx: ForallSortIdx, @@ -845,7 +845,7 @@ impl ParamType { /// The `args` field stores the generic arguments (Self type + other args), which can /// recursively contain other types including params, ADTs, and other projections. /// For example, ` as Iterator>::Item` would have `args = [Map]`. -#[derive(Debug, Clone)] +#[derive(Debug, Clone, PartialEq, Eq, Hash)] pub struct AliasType { forall_sort_idx: ForallSortIdx, args: Vec>, @@ -890,7 +890,7 @@ impl AliasType { } /// An array type. -#[derive(Debug, Clone)] +#[derive(Debug, Clone, PartialEq, Eq, Hash)] pub struct ArrayType { pub index: Box>, pub elem: Box>, @@ -973,7 +973,7 @@ impl ArrayType { } /// An underlying type of a refinement type. -#[derive(Debug, Clone)] +#[derive(Debug, Clone, PartialEq, Eq, Hash)] pub enum Type { Int, Bool, @@ -1413,7 +1413,7 @@ impl ShiftExistential for RefinedTypeVar { /// A formula, potentially equipped with an existential quantifier. /// /// Note: This is not to be confused with [`crate::chc::Formula`] in the [`crate::chc`] module, which is a different notion. -#[derive(Debug, Clone)] +#[derive(Debug, Clone, PartialEq, Eq, Hash)] pub struct Formula { pub existentials: IndexVec, pub body: chc::Body, @@ -1685,7 +1685,7 @@ impl Instantiator { } /// A refinement type. -#[derive(Debug, Clone)] +#[derive(Debug, Clone, PartialEq, Eq, Hash)] pub struct RefinedType { pub ty: Type, pub refinement: Refinement, From 5aa82f42f7d6472903ea78ec5c15d4a6eeee6597 Mon Sep 17 00:00:00 2001 From: coeff-aij <175928954+coeff-aij@users.noreply.github.com> Date: Mon, 22 Jun 2026 01:06:57 +0900 Subject: [PATCH 067/142] revert: comment out of extern spec for Option::unwrap_or_else() --- std.rs | 26 +++++++++++++------------- 1 file changed, 13 insertions(+), 13 deletions(-) diff --git a/std.rs b/std.rs index 3ddb4be5..87e5bde2 100644 --- a/std.rs +++ b/std.rs @@ -465,19 +465,19 @@ where Option::map(opt, f) } -// #[thrust::extern_spec_fn] -// #[thrust_macros::requires(opt != None || thrust_macros::pre!(f()))] -// #[thrust_macros::ensures( -// (opt != None && Some(result) == opt) -// || (opt == None && thrust_macros::post!(f(), result)) -// )] -// fn _extern_spec_option_unwrap_or_else(opt: Option, f: F) -> T -// where -// T: thrust_models::Model, T::Ty: PartialEq, -// F: FnOnce() -> T, -// { -// Option::unwrap_or_else(opt, f) -// } +#[thrust::extern_spec_fn] +#[thrust_macros::requires(opt != None || thrust_macros::pre!(f()))] +#[thrust_macros::ensures( + (opt != None && Some(result) == opt) + || (opt == None && thrust_macros::post!(f(), result)) +)] +fn _extern_spec_option_unwrap_or_else(opt: Option, f: F) -> T +where + T: thrust_models::Model, T::Ty: PartialEq, + F: FnOnce() -> T, +{ + Option::unwrap_or_else(opt, f) +} #[thrust::extern_spec_fn] #[thrust_macros::requires(true)] From 45d0bc4fe24294dbff46608eccd5558bc138b831 Mon Sep 17 00:00:00 2001 From: coeff-aij <175928954+coeff-aij@users.noreply.github.com> Date: Wed, 24 Jun 2026 22:04:27 +0900 Subject: [PATCH 068/142] fix: insert ForallPred for the last argument of closure --- src/analyze/annot_fn.rs | 27 +++++++++++++-------------- 1 file changed, 13 insertions(+), 14 deletions(-) diff --git a/src/analyze/annot_fn.rs b/src/analyze/annot_fn.rs index ce9dcb7a..e479ceba 100644 --- a/src/analyze/annot_fn.rs +++ b/src/analyze/annot_fn.rs @@ -484,11 +484,9 @@ impl<'a, 'tcx> AnnotFnTranslator<'a, 'tcx> { let free = |idx| chc::Term::var(rty::RefinedTypeVar::Free(idx)); let value = chc::Term::var(rty::RefinedTypeVar::Value); - let receiver = rty::FunctionParamIdx::from_usize(0); let args: Vec<_> = params .iter() .enumerate() - .skip(1) .map(|(idx, _)| free(rty::FunctionParamIdx::from_usize(idx))) .collect(); @@ -512,20 +510,21 @@ impl<'a, 'tcx> AnnotFnTranslator<'a, 'tcx> { ); self.register_forall_pred(post_pred.clone()); - params[receiver].extend_refinement( - chc::Atom::new( - pre_pred.into(), - [vec![value.clone()], args.clone()].concat(), - ) - .into(), - ); + params + .iter_mut() + .last() + .expect("Closure should have at least one argument.") + .extend_refinement({ + let (args_front, _args_last) = args.split_at(args.len() - 1); + chc::Atom::new( + pre_pred.into(), + [args_front, std::slice::from_ref(&value.clone())].concat(), + ) + .into() + }); ret.extend_refinement( - chc::Atom::new( - post_pred.into(), - [vec![free(receiver)], args, vec![value.clone()]].concat(), - ) - .into(), + chc::Atom::new(post_pred.into(), [args, vec![value.clone()]].concat()).into(), ); let ret = Box::new(ret); From 9eac0a59d5a830ae3494f786f4cadd2fe191bd2c Mon Sep 17 00:00:00 2001 From: coeff-aij <175928954+coeff-aij@users.noreply.github.com> Date: Thu, 25 Jun 2026 00:42:08 +0900 Subject: [PATCH 069/142] add: positive test cases for traits --- .../ui/pass/traits/annot_simple_loop_self.rs | 30 +++++++ tests/ui/pass/traits/loop_unbound.rs | 28 +++++++ tests/ui/pass/traits/multi_params.rs | 32 +++++++ tests/ui/pass/traits/option_map.rs | 25 ++++++ .../ui/pass/traits/simple_loop_call_multi.rs | 83 +++++++++++++++++++ 5 files changed, 198 insertions(+) create mode 100644 tests/ui/pass/traits/annot_simple_loop_self.rs create mode 100644 tests/ui/pass/traits/loop_unbound.rs create mode 100644 tests/ui/pass/traits/multi_params.rs create mode 100644 tests/ui/pass/traits/option_map.rs create mode 100644 tests/ui/pass/traits/simple_loop_call_multi.rs diff --git a/tests/ui/pass/traits/annot_simple_loop_self.rs b/tests/ui/pass/traits/annot_simple_loop_self.rs new file mode 100644 index 00000000..74671f6f --- /dev/null +++ b/tests/ui/pass/traits/annot_simple_loop_self.rs @@ -0,0 +1,30 @@ +//@check-pass +//@compile-flags: -C debug-assertions=off +//@rustc-env: THRUST_SOLVER=tests/thrust-pcsat-wrapper THRUST_SOLVER_TIMEOUT_SECS=60 + +#[thrust_macros::context] +trait A { + #[thrust_macros::requires(Self::p(*self, x))] + #[thrust_macros::ensures(Self::p(*self, result))] + fn f(&self, x: i64) -> i64; + + #[thrust_macros::predicate] + fn p(self, x: i64) -> bool; +} + +#[thrust_macros::invariant_context] +#[thrust_macros::requires(T::p(*a, x))] +#[thrust_macros::ensures(T::p(*a, result))] +fn target(a: &T, x: i64) -> i64 { + let mut v = x; + let mut i = 0; + while i < 3 { + thrust_macros::invariant!(|a: &T, v: i64| T::p(*a, v)); + v = a.f(v); + i += 1; + } + + v +} + +fn main() {} diff --git a/tests/ui/pass/traits/loop_unbound.rs b/tests/ui/pass/traits/loop_unbound.rs new file mode 100644 index 00000000..9677be02 --- /dev/null +++ b/tests/ui/pass/traits/loop_unbound.rs @@ -0,0 +1,28 @@ +//@check-pass +//@compile-flags: -C debug-assertions=off +//@rustc-env: THRUST_SOLVER=tests/thrust-pcsat-wrapper THRUST_SOLVER_TIMEOUT_SECS=60 + +#[thrust_macros::context] +trait A { + #[thrust_macros::requires(Self::p(x))] + #[thrust_macros::ensures(Self::p(result))] + fn f(&self, x: i64) -> i64; + + #[thrust_macros::predicate] + fn p(x: i64) -> bool; +} + +#[thrust_macros::requires(T::p(x))] +#[thrust_macros::ensures(T::p(result))] +fn target(a: &T, x: i64, n: u64) -> i64 { + let mut v = x; + let mut i = 0; + while i < n { + v = a.f(v); + i += 1; + } + + v +} + +fn main() {} diff --git a/tests/ui/pass/traits/multi_params.rs b/tests/ui/pass/traits/multi_params.rs new file mode 100644 index 00000000..591ef705 --- /dev/null +++ b/tests/ui/pass/traits/multi_params.rs @@ -0,0 +1,32 @@ +//@check-pass +//@compile-flags: -C debug-assertions=off +//@rustc-env: THRUST_SOLVER=tests/thrust-pcsat-wrapper THRUST_SOLVER_TIMEOUT_SECS=60 + +#[thrust_macros::context] +trait A { + #[thrust_macros::requires(Self::p(*self))] + #[thrust_macros::ensures(Self::p(!self))] + fn f(&mut self); + #[thrust_macros::requires(true)] + #[thrust_macros::ensures(Self::p(!self))] + fn g(&mut self); + + #[thrust_macros::predicate] + fn p(self) -> bool; +} + +#[thrust_macros::requires(T::p(*x) && n > 0)] +#[thrust_macros::ensures(T::p(!x) && S::p(!y))] +fn multi_loop<'a, T: A, S: A>(x: &mut T, y: &mut S, n: u64) { + let mut i = 0; + while i < n { // The loop depends on P + x.f(); i += 1; + } + + let mut j = 0; + while j < n { // The loop depends on Q + y.g(); j += 1; + } +} + +fn main() {} diff --git a/tests/ui/pass/traits/option_map.rs b/tests/ui/pass/traits/option_map.rs new file mode 100644 index 00000000..e2b0a4b3 --- /dev/null +++ b/tests/ui/pass/traits/option_map.rs @@ -0,0 +1,25 @@ +//@check-pass +//@compile-flags: -C debug-assertions=off +//@rustc-env: THRUST_SOLVER=tests/thrust-pcsat-wrapper THRUST_SOLVER_TIMEOUT_SECS=60 + +#[thrust_macros::requires( + opt == None || thrust_models::exists(|i| opt == Some(i) && thrust_macros::pre!(f(i))) +)] +#[thrust_macros::ensures( + (opt == None && result == None) + || thrust_models::exists(|i| thrust_models::exists(|j| + opt == Some(i) && thrust_macros::post!(f(i), j) && result == Some(j))) +)] +fn map(opt: Option, f: F) -> Option +where + T: thrust_models::Model, T::Ty: PartialEq, + U: thrust_models::Model, U::Ty: PartialEq, + F: FnOnce(T) -> U, +{ + match opt { + Some(i) => Some(f(i)), + None => None, + } +} + +fn main() {} diff --git a/tests/ui/pass/traits/simple_loop_call_multi.rs b/tests/ui/pass/traits/simple_loop_call_multi.rs new file mode 100644 index 00000000..92254399 --- /dev/null +++ b/tests/ui/pass/traits/simple_loop_call_multi.rs @@ -0,0 +1,83 @@ +//@check-pass +//@compile-flags: -C debug-assertions=off +//@rustc-env: THRUST_SOLVER=tests/thrust-pcsat-wrapper THRUST_SOLVER_TIMEOUT_SECS=60 + +#[thrust_macros::context] +trait A { + #[thrust_macros::requires(Self::p(*self))] + #[thrust_macros::ensures(Self::p(!self))] + fn f(&mut self); + #[thrust_macros::requires(true)] + #[thrust_macros::ensures(Self::p(!self))] + fn g(&mut self); + + #[thrust_macros::predicate] + fn p(self) -> bool; +} + +#[thrust_macros::requires(T::p(*x) && n > 0)] +#[thrust_macros::ensures(T::p(!x))] +fn repeat(x: &mut T, n: u64) { + let mut i = 0; + while i < n { + x.f(); + i += 1; + } +} + +#[derive(PartialEq)] +struct X(i64); + +impl thrust_models::Model for X { + type Ty = X; +} + +#[thrust_macros::context] +impl A for X { + fn f(&mut self) { + self.0 += 1 + } + + fn g(&mut self) { + if !(self.0 > 0) { self.0 = 1 - self.0 } + } + + #[thrust_macros::predicate] + fn p(self) -> bool { + "(> (tuple_proj.0 self_) 0)"; true + } +} + +#[derive(PartialEq)] +struct Y(i64); + +impl thrust_models::Model for Y { + type Ty = Y; +} + +#[thrust_macros::context] +impl A for Y { + fn f(&mut self) { + self.0 += 1 + } + + fn g(&mut self) { + if !(self.0 > 0) { self.0 = 1 - self.0 } + } + + #[thrust_macros::predicate] + fn p(self) -> bool { + "(> (tuple_proj.0 self_) 0)"; true + } +} + +#[thrust_macros::requires(true)] +#[thrust_macros::ensures(X::p(result.0) && Y::p(result.1))] +fn target() -> (X, Y) { + let (mut x, mut y) = (X(1), Y(-1)); + repeat(&mut x, 3); + repeat(&mut y, 5); + (x, y) +} + +fn main() {} From 6e78502f5115bfed3d24d1390cae71e18c1db1e3 Mon Sep 17 00:00:00 2001 From: coeff-aij <175928954+coeff-aij@users.noreply.github.com> Date: Thu, 25 Jun 2026 19:31:32 +0900 Subject: [PATCH 070/142] fix: instantiate generic args during construction of argment types for ForallPred --- src/analyze/annot_fn.rs | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/src/analyze/annot_fn.rs b/src/analyze/annot_fn.rs index 11b73a05..59637e8b 100644 --- a/src/analyze/annot_fn.rs +++ b/src/analyze/annot_fn.rs @@ -1037,12 +1037,10 @@ impl<'a, 'tcx> AnnotFnTranslator<'a, 'tcx> { .map(|ty| self.type_builder.build(ty).to_sort()) .collect(); - let typeck_result = self.tcx.typeck(self.local_def_id); let params = args .iter() .map(|expr| { - let ty = typeck_result.expr_ty(expr); - self.type_builder.build(ty).to_sort() + self.type_builder.build(self.expr_ty(expr)).to_sort() }) .collect(); From 60ce95a482dc8c2284aa05d2655e3bef89cada0c Mon Sep 17 00:00:00 2001 From: coeff-aij <175928954+coeff-aij@users.noreply.github.com> Date: Thu, 25 Jun 2026 20:39:49 +0900 Subject: [PATCH 071/142] fix: bypass instantiation when generic args are unknown --- src/analyze/annot_fn.rs | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/src/analyze/annot_fn.rs b/src/analyze/annot_fn.rs index 59637e8b..19a3d1ce 100644 --- a/src/analyze/annot_fn.rs +++ b/src/analyze/annot_fn.rs @@ -611,9 +611,11 @@ impl<'a, 'tcx> AnnotFnTranslator<'a, 'tcx> { } fn node_arg_type_at(&self, hir_id: HirId, idx: usize) -> rty::Type { - let generic_args = self.typeck.node_args(hir_id); - let generic_args = - mir_ty::EarlyBinder::bind(generic_args).instantiate(self.tcx, self.generic_args); + let mut generic_args = self.typeck.node_args(hir_id); + if !self.generic_args.is_empty() { + generic_args = + mir_ty::EarlyBinder::bind(generic_args).instantiate(self.tcx, self.generic_args); + } let elem_ty = generic_args.type_at(idx); self.type_builder.build(elem_ty) } From 06a003a38128f5b0423ece0e8255ab5735bdfcdf Mon Sep 17 00:00:00 2001 From: coeff-aij <175928954+coeff-aij@users.noreply.github.com> Date: Fri, 26 Jun 2026 00:34:59 +0900 Subject: [PATCH 072/142] refine: lift closure_trait_args / closure_trait_ret to TypeBuilder --- src/analyze/annot_fn.rs | 41 ++------------------------- src/refine/template.rs | 62 +++++++++++++++++++++++++++++++++++++++++ 2 files changed, 64 insertions(+), 39 deletions(-) diff --git a/src/analyze/annot_fn.rs b/src/analyze/annot_fn.rs index 19a3d1ce..dbc17e1a 100644 --- a/src/analyze/annot_fn.rs +++ b/src/analyze/annot_fn.rs @@ -404,35 +404,7 @@ impl<'a, 'tcx> AnnotFnTranslator<'a, 'tcx> { param_ty: mir_ty::ParamTy, pred: mir_ty::TraitPredicate<'tcx>, ) -> Option>> { - let trait_ref = pred.trait_ref; - if trait_ref.self_ty() != param_ty.to_ty(self.tcx) { - return None; - } - tracing::debug!(?trait_ref.args); - - let receiver_type = self.type_builder.build(trait_ref.args.type_at(0)); - - use mir_ty::ClosureKind::*; - let receiver_type = match self.tcx.fn_trait_kind_from_def_id(trait_ref.def_id)? { - Fn => rty::PointerType::immut_to(receiver_type).into(), - FnMut => rty::PointerType::mut_to(receiver_type).into(), - FnOnce => receiver_type, - }; - - let mir_ty::Tuple(other_params) = trait_ref.args.type_at(1).kind() else { - panic!() - }; - - let other_params = other_params - .iter() - .map(|ty| self.type_builder.build(ty).vacuous()); - let params = std::iter::once(receiver_type.vacuous()) - .chain(other_params) - .map(rty::RefinedType::unrefined) - .collect(); - - tracing::debug!("found the signature for closure trait: {params:#?}"); - Some(params) + self.type_builder.closure_trait_args(param_ty, pred) } #[tracing::instrument(skip(self))] @@ -441,16 +413,7 @@ impl<'a, 'tcx> AnnotFnTranslator<'a, 'tcx> { param_ty: mir_ty::ParamTy, pred: mir_ty::ProjectionPredicate<'tcx>, ) -> Option> { - let projection = pred.projection_term; - if projection.def_id != self.tcx.lang_items().fn_once_output()? - || projection.args.type_at(0) != param_ty.to_ty(self.tcx) - { - return None; - } - - let ret_ty = self.type_builder.build(pred.term.expect_type()).vacuous(); - tracing::debug!(?ret_ty); - Some(rty::RefinedType::unrefined(ret_ty)) + self.type_builder.closure_trait_ret(param_ty, pred) } fn register_forall_pred(&self, forall_pred: chc::ForallPred) { diff --git a/src/refine/template.rs b/src/refine/template.rs index a79a40e6..2d9786bc 100644 --- a/src/refine/template.rs +++ b/src/refine/template.rs @@ -464,6 +464,68 @@ impl<'tcx> TypeBuilder<'tcx> { abi, } } + + /// Extracts the parameter list for a `Fn` / `FnMut` / `FnOnce` trait predicate + /// whose `Self` type matches `param_ty`. Returns `None` otherwise. + /// + /// The first parameter is the closure value (wrapped in a `&` / `&mut` pointer + /// for `Fn` / `FnMut`, or owned for `FnOnce`), followed by the logical arguments + /// as a single tuple matching the call-site shape produced by + /// `>::call(...)`. + #[tracing::instrument(skip(self))] + pub(crate) fn closure_trait_args( + &self, + param_ty: mir_ty::ParamTy, + pred: mir_ty::TraitPredicate<'tcx>, + ) -> Option>> { + let trait_ref = pred.trait_ref; + if trait_ref.self_ty() != param_ty.to_ty(self.tcx) { + return None; + } + tracing::debug!(?trait_ref.args); + + let receiver_type = self.build(trait_ref.args.type_at(0)); + + use mir_ty::ClosureKind::*; + let receiver_type = match self.tcx.fn_trait_kind_from_def_id(trait_ref.def_id)? { + Fn => rty::PointerType::immut_to(receiver_type).into(), + FnMut => rty::PointerType::mut_to(receiver_type).into(), + FnOnce => receiver_type, + }; + + let mir_ty::Tuple(other_params) = trait_ref.args.type_at(1).kind() else { + panic!("Closure should have at least one argument.") + }; + + let other_params = other_params.iter().map(|ty| self.build(ty).vacuous()); + let params = std::iter::once(receiver_type.vacuous()) + .chain(other_params) + .map(rty::RefinedType::unrefined) + .collect(); + + tracing::debug!("found the signature for closure trait: {params:#?}"); + Some(params) + } + + /// Extracts the return type refinement for `::Output` projection + /// where `F = param_ty`. Returns `None` otherwise. + #[tracing::instrument(skip(self))] + pub(crate) fn closure_trait_ret( + &self, + param_ty: mir_ty::ParamTy, + pred: mir_ty::ProjectionPredicate<'tcx>, + ) -> Option> { + let projection = pred.projection_term; + if projection.def_id != self.tcx.lang_items().fn_once_output()? + || projection.args.type_at(0) != param_ty.to_ty(self.tcx) + { + return None; + } + + let ret_ty = self.build(pred.term.expect_type()).vacuous(); + tracing::debug!(?ret_ty); + Some(rty::RefinedType::unrefined(ret_ty)) + } } /// Translates [`mir_ty::Ty`] to [`rty::Type`] using templates for refinements. From b9c1413c09c2cbb503213ac8ea99b7477e23a142 Mon Sep 17 00:00:00 2001 From: coeff-aij <175928954+coeff-aij@users.noreply.github.com> Date: Fri, 26 Jun 2026 00:36:52 +0900 Subject: [PATCH 073/142] refine: add TypeBuilder::build_closure_type_for_param --- src/analyze/annot_fn.rs | 79 +++------------------------------ src/refine/template.rs | 98 +++++++++++++++++++++++++++++++++++++++++ 2 files changed, 105 insertions(+), 72 deletions(-) diff --git a/src/analyze/annot_fn.rs b/src/analyze/annot_fn.rs index dbc17e1a..36b9228f 100644 --- a/src/analyze/annot_fn.rs +++ b/src/analyze/annot_fn.rs @@ -399,6 +399,7 @@ impl<'a, 'tcx> AnnotFnTranslator<'a, 'tcx> { } #[tracing::instrument(skip(self))] + #[allow(dead_code)] fn closure_trait_args( &self, param_ty: mir_ty::ParamTy, @@ -408,6 +409,7 @@ impl<'a, 'tcx> AnnotFnTranslator<'a, 'tcx> { } #[tracing::instrument(skip(self))] + #[allow(dead_code)] fn closure_trait_ret( &self, param_ty: mir_ty::ParamTy, @@ -424,78 +426,11 @@ impl<'a, 'tcx> AnnotFnTranslator<'a, 'tcx> { } fn type_param_as_callable_sig(&self, param_ty: mir_ty::ParamTy) -> Option { - let param_ty = self - .instantiate_generics(param_ty, self.generic_args) - .unwrap_or(param_ty); - let mut predicates = self - .tcx - .predicates_of(self.local_def_id) - .predicates - .iter() - .map(|(clause, _)| { - self.instantiate_generics(*clause, self.generic_args) - .unwrap_or(*clause) - }); - - let mut params = predicates.clone().find_map(|clause| { - self.closure_trait_args(param_ty, clause.as_trait_clause()?.skip_binder()) - })?; - let mut ret = predicates.find_map(|clause| { - self.closure_trait_ret(param_ty, clause.as_projection_clause()?.skip_binder()) - })?; - - let free = |idx| chc::Term::var(rty::RefinedTypeVar::Free(idx)); - let value = chc::Term::var(rty::RefinedTypeVar::Value); - - let args: Vec<_> = params - .iter() - .enumerate() - .map(|(idx, _)| free(rty::FunctionParamIdx::from_usize(idx))) - .collect(); - - let type_params = vec![self.type_builder.build(param_ty.to_ty(self.tcx)).to_sort()]; - let mut params_sort: Vec = params.iter().map(|rty| rty.ty.to_sort()).collect(); - let ret_sort = ret.ty.to_sort(); - - let pre_pred = refine::closure_pre_forall_pred( - self.tcx, - self.type_builder.owner_fn_id(), - type_params.clone(), - params_sort.clone(), - ); - self.register_forall_pred(pre_pred.clone()); - params_sort.push(ret_sort); - let post_pred = refine::closure_post_forall_pred( - self.tcx, - self.type_builder.owner_fn_id(), - type_params, - params_sort, - ); - self.register_forall_pred(post_pred.clone()); - - params - .iter_mut() - .last() - .expect("Closure should have at least one argument.") - .extend_refinement({ - let (args_front, _args_last) = args.split_at(args.len() - 1); - chc::Atom::new( - pre_pred.into(), - [args_front, std::slice::from_ref(&value.clone())].concat(), - ) - .into() - }); - - ret.extend_refinement( - chc::Atom::new(post_pred.into(), [args, vec![value.clone()]].concat()).into(), - ); - let ret = Box::new(ret); - - Some(rty::FunctionType { - params, - ret, - abi: rty::FunctionAbi::RustCall, - }) + self.type_builder.build_closure_type_for_param( + param_ty, + self.local_def_id, + self.generic_args, + ) } /// Extracts the logical argument terms passed to `closure_precondition`/ diff --git a/src/refine/template.rs b/src/refine/template.rs index 2d9786bc..d90d2c1e 100644 --- a/src/refine/template.rs +++ b/src/refine/template.rs @@ -526,6 +526,104 @@ impl<'tcx> TypeBuilder<'tcx> { tracing::debug!(?ret_ty); Some(rty::RefinedType::unrefined(ret_ty)) } + + /// Builds the [`rty::FunctionType`] for a type parameter `param_ty` declared + /// on the function identified by `local_def_id`, when `param_ty` is bounded + /// by `Fn` / `FnMut` / `FnOnce`. + /// + /// `generic_args` are the generic arguments of the enclosing function + /// (the function whose body is being type-checked). When the enclosing + /// function is itself generic, `predicates_of(local_def_id)` is the raw + /// predicate list which is then instantiated with `generic_args`. When + /// `generic_args` is empty, predicates are used un-instantiated. + /// + /// Returns `None` if `param_ty` has no `Fn` / `FnMut` / `FnOnce` trait bound. + /// As a side effect, the closure pre/post forall predicates are registered + /// with the [`chc::System`]. + pub fn build_closure_type_for_param( + &self, + param_ty: mir_ty::ParamTy, + local_def_id: rustc_hir::def_id::LocalDefId, + generic_args: mir_ty::GenericArgsRef<'tcx>, + ) -> Option { + let param_ty = if !generic_args.is_empty() { + mir_ty::EarlyBinder::bind(param_ty).instantiate(self.tcx, generic_args) + } else { + param_ty + }; + let mut predicates = self + .tcx + .predicates_of(local_def_id.to_def_id()) + .predicates + .iter() + .map(|(clause, _)| { + if !generic_args.is_empty() { + mir_ty::EarlyBinder::bind(*clause).instantiate(self.tcx, generic_args) + } else { + *clause + } + }); + + let mut params = predicates.clone().find_map(|clause| { + self.closure_trait_args(param_ty, clause.as_trait_clause()?.skip_binder()) + })?; + let mut ret = predicates.find_map(|clause| { + self.closure_trait_ret(param_ty, clause.as_projection_clause()?.skip_binder()) + })?; + + let free = |idx| chc::Term::var(rty::RefinedTypeVar::Free(idx)); + let value = chc::Term::var(rty::RefinedTypeVar::Value); + + let args: Vec<_> = params + .iter() + .enumerate() + .map(|(idx, _)| free(rty::FunctionParamIdx::from_usize(idx))) + .collect(); + + let type_params = vec![self.build(param_ty.to_ty(self.tcx)).to_sort()]; + let mut params_sort: Vec = params.iter().map(|rty| rty.ty.to_sort()).collect(); + let ret_sort = ret.ty.to_sort(); + + let pre_pred = refine::closure_pre_forall_pred( + self.tcx, + self.owner_fn_id, + type_params.clone(), + params_sort.clone(), + ); + self.system + .borrow_mut() + .register_forall_pred(pre_pred.clone()); + params_sort.push(ret_sort); + let post_pred = + refine::closure_post_forall_pred(self.tcx, self.owner_fn_id, type_params, params_sort); + self.system + .borrow_mut() + .register_forall_pred(post_pred.clone()); + + params + .iter_mut() + .last() + .expect("Closure should have at least one argument.") + .extend_refinement({ + let (args_front, _args_last) = args.split_at(args.len() - 1); + chc::Atom::new( + pre_pred.into(), + [args_front, std::slice::from_ref(&value.clone())].concat(), + ) + .into() + }); + + ret.extend_refinement( + chc::Atom::new(post_pred.into(), [args, vec![value.clone()]].concat()).into(), + ); + let ret = Box::new(ret); + + Some(rty::FunctionType { + params, + ret, + abi: rty::FunctionAbi::RustCall, + }) + } } /// Translates [`mir_ty::Ty`] to [`rty::Type`] using templates for refinements. From dd10f29109d2e72dc03234b4ecb2120b78065b9c Mon Sep 17 00:00:00 2001 From: coeff-aij <175928954+coeff-aij@users.noreply.github.com> Date: Fri, 26 Jun 2026 00:38:43 +0900 Subject: [PATCH 074/142] analyze/annot_fn: drop closure_trait helpers, switch to TypeBuilder entry --- src/analyze/annot_fn.rs | 34 +++++----------------------------- src/refine/template.rs | 4 ++-- 2 files changed, 7 insertions(+), 31 deletions(-) diff --git a/src/analyze/annot_fn.rs b/src/analyze/annot_fn.rs index 36b9228f..03ae339a 100644 --- a/src/analyze/annot_fn.rs +++ b/src/analyze/annot_fn.rs @@ -380,7 +380,11 @@ impl<'a, 'tcx> AnnotFnTranslator<'a, 'tcx> { let mir_ty::TyKind::Closure(def_id, args) = closure_ty.kind() else { if let mir_ty::TyKind::Param(ty) = closure_ty.kind() { tracing::debug!("ParamTy is found: {ty:?}"); - let closure_fun_ty = self.type_param_as_callable_sig(*ty); + let closure_fun_ty = self.type_builder.build_closure_type_for_param( + *ty, + self.local_def_id, + self.generic_args, + ); tracing::debug!( "the obtained FunctionType for the closure {ty:?}: {closure_fun_ty:#?}" ); @@ -398,26 +402,6 @@ impl<'a, 'tcx> AnnotFnTranslator<'a, 'tcx> { .known_function_ty_with_args(*def_id, self.tcx.mk_args(args.as_closure().parent_args())) } - #[tracing::instrument(skip(self))] - #[allow(dead_code)] - fn closure_trait_args( - &self, - param_ty: mir_ty::ParamTy, - pred: mir_ty::TraitPredicate<'tcx>, - ) -> Option>> { - self.type_builder.closure_trait_args(param_ty, pred) - } - - #[tracing::instrument(skip(self))] - #[allow(dead_code)] - fn closure_trait_ret( - &self, - param_ty: mir_ty::ParamTy, - pred: mir_ty::ProjectionPredicate<'tcx>, - ) -> Option> { - self.type_builder.closure_trait_ret(param_ty, pred) - } - fn register_forall_pred(&self, forall_pred: chc::ForallPred) { self.analyzer .system @@ -425,14 +409,6 @@ impl<'a, 'tcx> AnnotFnTranslator<'a, 'tcx> { .register_forall_pred(forall_pred.clone()); } - fn type_param_as_callable_sig(&self, param_ty: mir_ty::ParamTy) -> Option { - self.type_builder.build_closure_type_for_param( - param_ty, - self.local_def_id, - self.generic_args, - ) - } - /// Extracts the logical argument terms passed to `closure_precondition`/ /// `closure_postcondition`. The arguments are supplied as a single tuple (e.g. `(x,)` or /// `()`), whose elements are the logical arguments of the closure. diff --git a/src/refine/template.rs b/src/refine/template.rs index d90d2c1e..e5d1264f 100644 --- a/src/refine/template.rs +++ b/src/refine/template.rs @@ -473,7 +473,7 @@ impl<'tcx> TypeBuilder<'tcx> { /// as a single tuple matching the call-site shape produced by /// `>::call(...)`. #[tracing::instrument(skip(self))] - pub(crate) fn closure_trait_args( + fn closure_trait_args( &self, param_ty: mir_ty::ParamTy, pred: mir_ty::TraitPredicate<'tcx>, @@ -510,7 +510,7 @@ impl<'tcx> TypeBuilder<'tcx> { /// Extracts the return type refinement for `::Output` projection /// where `F = param_ty`. Returns `None` otherwise. #[tracing::instrument(skip(self))] - pub(crate) fn closure_trait_ret( + fn closure_trait_ret( &self, param_ty: mir_ty::ParamTy, pred: mir_ty::ProjectionPredicate<'tcx>, From 0657632e1733f2df53df7afd3e57b5b1b99893e1 Mon Sep 17 00:00:00 2001 From: coeff-aij <175928954+coeff-aij@users.noreply.github.com> Date: Fri, 26 Jun 2026 00:39:23 +0900 Subject: [PATCH 075/142] refine: restrict register_closure_type_param visibility to pub(crate) --- src/refine/template.rs | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/src/refine/template.rs b/src/refine/template.rs index e5d1264f..e5d8b2da 100644 --- a/src/refine/template.rs +++ b/src/refine/template.rs @@ -185,7 +185,11 @@ impl<'tcx> TypeBuilder<'tcx> { rty::AliasType::new(*index, args).into() } - pub fn register_closure_type_param(&self, type_param: TypeParam, fun_type: rty::FunctionType) { + pub(crate) fn register_closure_type_param( + &self, + type_param: TypeParam, + fun_type: rty::FunctionType, + ) { tracing::info!(?type_param, ?fun_type, "register_closure_type_param"); self.closure_type_params .borrow_mut() From 9f537abd7476a0a868923ca7c96f7d039ca24fd3 Mon Sep 17 00:00:00 2001 From: coeff-aij <175928954+coeff-aij@users.noreply.github.com> Date: Fri, 26 Jun 2026 00:41:07 +0900 Subject: [PATCH 076/142] analyze/local_def: eagerly precompute closure contracts for type params --- src/analyze/local_def.rs | 31 +++++++++++++++++++++++++++++++ 1 file changed, 31 insertions(+) diff --git a/src/analyze/local_def.rs b/src/analyze/local_def.rs index 51678f00..c2021979 100644 --- a/src/analyze/local_def.rs +++ b/src/analyze/local_def.rs @@ -401,6 +401,37 @@ impl<'tcx, 'ctx> Analyzer<'tcx, 'ctx> { .tcx .try_normalize_erasing_regions(mir_ty::TypingEnv::fully_monomorphized(), *input_ty) .unwrap_or(*input_ty); + + // Eagerly register closure type parameters (e.g. `F: Fn(...)` declared on + // this function) so that basic-block analysis can look up the contract + // when it later sees `>::call(...)`. + let param_ty = match inst.kind() { + mir_ty::TyKind::Param(p) => Some(*p), + mir_ty::TyKind::Ref(_, inner, _) => { + if let mir_ty::TyKind::Param(p) = inner.kind() { + Some(*p) + } else { + None + } + } + _ => None, + }; + if let Some(param_ty) = param_ty { + if let Some(fun_ty) = self.type_builder.build_closure_type_for_param( + param_ty, + self.local_def_id, + self.tcx.mk_args(&[]), + ) { + self.type_builder.register_closure_type_param( + analyze::TypeParam::GenericType( + self.type_builder.owner_fn_id(), + param_ty.index, + ), + fun_ty, + ); + } + } + let (fn_def_id, fn_args) = match inst.kind() { mir_ty::TyKind::Closure(def_id, args) => { (*def_id, self.tcx.mk_args(args.as_closure().parent_args())) From 7595088bdea244635110ae78f3b0e9873979780a Mon Sep 17 00:00:00 2001 From: coeff-aij <175928954+coeff-aij@users.noreply.github.com> Date: Fri, 26 Jun 2026 23:03:28 +0900 Subject: [PATCH 077/142] fix: try to normalize alias projections and allocate the same ForallSortIdx --- src/refine/template.rs | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/src/refine/template.rs b/src/refine/template.rs index e5d8b2da..46bd8ea5 100644 --- a/src/refine/template.rs +++ b/src/refine/template.rs @@ -171,6 +171,21 @@ impl<'tcx> TypeBuilder<'tcx> { } fn translate_alias_type(&self, ty: &mir_ty::AliasTy<'tcx>) -> rty::Type { + let projection = mir_ty::Ty::new_projection(self.tcx, ty.def_id, ty.args); + if let Ok(normalized) = self + .tcx + .try_normalize_erasing_regions(self.typing_env, projection) + { + if normalized != projection { + tracing::debug!( + "alias projection {:#?} normalized to {:#?}", + projection, + normalized + ); + return self.build(normalized); + } + } + let args: Vec> = ty.args.types().map(|t| self.build(t)).collect(); let mut type_params = self.type_params.borrow_mut(); tracing::debug!(?type_params); From e27f7ad68670df913544a456980f1ee75c68c19e Mon Sep 17 00:00:00 2001 From: coeff-aij <175928954+coeff-aij@users.noreply.github.com> Date: Fri, 26 Jun 2026 23:05:39 +0900 Subject: [PATCH 078/142] fix: use impl block's DefId for recognition of type parameters in impl --- src/analyze/annot_fn.rs | 5 ++++- src/analyze/basic_block.rs | 2 +- src/analyze/local_def.rs | 4 ++-- src/refine/template.rs | 19 +++++++++++++++---- 4 files changed, 22 insertions(+), 8 deletions(-) diff --git a/src/analyze/annot_fn.rs b/src/analyze/annot_fn.rs index 03ae339a..89e7fb66 100644 --- a/src/analyze/annot_fn.rs +++ b/src/analyze/annot_fn.rs @@ -390,7 +390,10 @@ impl<'a, 'tcx> AnnotFnTranslator<'a, 'tcx> { ); if let Some(closure_fun_ty) = closure_fun_ty.clone() { self.type_builder.register_closure_type_param( - analyze::TypeParam::GenericType(self.type_builder.owner_fn_id(), ty.index), + analyze::TypeParam::GenericType( + self.type_builder.param_def_id(ty), + ty.index, + ), closure_fun_ty, ); }; diff --git a/src/analyze/basic_block.rs b/src/analyze/basic_block.rs index 1f8aeed3..a2042a34 100644 --- a/src/analyze/basic_block.rs +++ b/src/analyze/basic_block.rs @@ -856,7 +856,7 @@ impl<'tcx, 'ctx> Analyzer<'tcx, 'ctx> { ResolvedCallable::Closure(*closure_def_id, parent_args) } mir_ty::TyKind::Param(ty) => ResolvedCallable::Generic(TypeParam::GenericType( - self.type_builder.owner_fn_id(), + self.type_builder.param_def_id(ty), ty.index, )), kind => { diff --git a/src/analyze/local_def.rs b/src/analyze/local_def.rs index c2021979..7d14345b 100644 --- a/src/analyze/local_def.rs +++ b/src/analyze/local_def.rs @@ -389,7 +389,7 @@ impl<'tcx, 'ctx> Analyzer<'tcx, 'ctx> { .trait_item_def_id .unwrap(); self.ctx - .def_ty_with_args(trait_item_did, trait_ref.args, trait_ref.def_id) + .def_ty_with_args(trait_item_did, trait_ref.args, impl_did) } // TODO: Remove this eager precompute together with @@ -424,7 +424,7 @@ impl<'tcx, 'ctx> Analyzer<'tcx, 'ctx> { ) { self.type_builder.register_closure_type_param( analyze::TypeParam::GenericType( - self.type_builder.owner_fn_id(), + self.type_builder.param_def_id(¶m_ty), param_ty.index, ), fun_ty, diff --git a/src/refine/template.rs b/src/refine/template.rs index 46bd8ea5..b4bd65f5 100644 --- a/src/refine/template.rs +++ b/src/refine/template.rs @@ -125,6 +125,16 @@ impl<'tcx> TypeBuilder<'tcx> { self.owner_fn_id } + /// Returns the def_id of the declaration site of the given type parameter. + /// + /// For impl methods, an inherited parameter (e.g. the impl's `I`) reports + /// the def_id of the parameter declared on the impl block, so all uses + /// of that parameter across the impl's methods share a single cache key. + pub fn param_def_id(&self, ty: &mir_ty::ParamTy) -> DefId { + let generics = self.tcx.generics_of(self.owner_fn_id); + generics.param_at(ty.index as usize, self.tcx).def_id + } + fn translate_param_type(&self, ty: &mir_ty::ParamTy) -> rty::Type { // FIXME: // `__ThrustSelf` is currently treated as a distinct `ParamTy` from `Self`, @@ -152,18 +162,19 @@ impl<'tcx> TypeBuilder<'tcx> { .get(&ty.index) .expect("unknown type param idx"); - tracing::debug!("translating ParamTy {ty:?}..."); + let param_def_id = self.param_def_id(ty); + tracing::debug!("translating ParamTy {ty:?} (decl={param_def_id:?})..."); let mut type_params = self.type_params.borrow_mut(); let forall_sort_idx = type_params - .entry(TypeParam::GenericType(self.owner_fn_id, ty.index)) + .entry(TypeParam::GenericType(param_def_id, ty.index)) .or_insert_with(|| { let idx = self.system.borrow_mut().new_forall_sort(); tracing::debug!( - "issue the new ForallSortIdx {} for ParamTy {:?} at {:?}.", + "issue the new ForallSortIdx {} for ParamTy {:?} (decl={:?}).", idx, ty, - self.owner_fn_id + param_def_id ); idx }); From 2b696ce021ad519008010def2961f3d03c8f3c39 Mon Sep 17 00:00:00 2001 From: coeff-aij <175928954+coeff-aij@users.noreply.github.com> Date: Fri, 26 Jun 2026 23:16:37 +0900 Subject: [PATCH 079/142] change: use GenericDefTy for unannotated generic functions --- src/analyze/crate_.rs | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/src/analyze/crate_.rs b/src/analyze/crate_.rs index e1e66b30..4fc2feab 100644 --- a/src/analyze/crate_.rs +++ b/src/analyze/crate_.rs @@ -120,12 +120,10 @@ impl<'tcx, 'ctx> Analyzer<'tcx, 'ctx> { }) { self.ctx .register_deferred_def_without_analysis(owner_fn_id, local_def_id); - } else if analyzer.is_fully_annotated() { + } else { let expected = analyzer.expected_ty(); self.ctx .register_generic_def(owner_fn_id, local_def_id, Some(expected)); - } else { - self.ctx.register_deferred_def(owner_fn_id, local_def_id); } } else { let expected = analyzer.expected_ty(); From 083f3129b57611bda63ba329663e3b4cce103c32 Mon Sep 17 00:00:00 2001 From: coeff-aij <175928954+coeff-aij@users.noreply.github.com> Date: Sat, 27 Jun 2026 00:29:09 +0900 Subject: [PATCH 080/142] remove: instantiation which breaks alias types which are already normalized --- src/analyze.rs | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/src/analyze.rs b/src/analyze.rs index 5c9cf9bc..59b7ff4d 100644 --- a/src/analyze.rs +++ b/src/analyze.rs @@ -555,10 +555,7 @@ impl<'tcx> Analyzer<'tcx> { .owner_fn_id(caller_def_id) .generic_args(generic_args); - let mut expected = analyzer.expected_ty(); - // parameters in annotations are left as params - // TODO: remove this after annotation V2 - Self::instantiate_generic_args(&mut expected, generic_args, &type_builder); + let expected = analyzer.expected_ty(); instantiated_ty_cache .borrow_mut() .insert(generic_args, expected.clone()); From d4252d9a0f51ba70bfcbea9a2496d36f57338cd3 Mon Sep 17 00:00:00 2001 From: coeff-aij <175928954+coeff-aij@users.noreply.github.com> Date: Sat, 27 Jun 2026 04:28:34 +0900 Subject: [PATCH 081/142] add: gather closure param type from parent impl block --- src/analyze/local_def.rs | 46 ++++++++++++++++++++++++++++++++++++++++ 1 file changed, 46 insertions(+) diff --git a/src/analyze/local_def.rs b/src/analyze/local_def.rs index 7d14345b..8269b656 100644 --- a/src/analyze/local_def.rs +++ b/src/analyze/local_def.rs @@ -446,6 +446,52 @@ impl<'tcx, 'ctx> Analyzer<'tcx, 'ctx> { .ctx .def_ty_with_args(fn_def_id, fn_args, self.owner_fn_id); } + + let parent_def_id = self.tcx.parent(self.local_def_id.to_def_id()); + if matches!( + self.tcx.def_kind(parent_def_id), + rustc_hir::def::DefKind::Impl { .. } + ) { + if let Some(impl_local_def_id) = parent_def_id.as_local() { + self.precompute_impl_closure_type_params(impl_local_def_id); + } + } + } + + /// Walks `impl_local_def_id`'s `predicates_of` and registers any + /// `Fn`/`FnMut`/`FnOnce` type parameters declared on the impl. + fn precompute_impl_closure_type_params(&mut self, impl_local_def_id: LocalDefId) { + for (clause, _) in self + .tcx + .predicates_of(impl_local_def_id.to_def_id()) + .predicates + .iter() + { + let Some(trait_clause) = clause.as_trait_clause() else { + continue; + }; + let trait_ref = trait_clause.skip_binder(); + if self + .tcx + .fn_trait_kind_from_def_id(trait_ref.def_id()) + .is_none() + { + continue; + } + let mir_ty::TyKind::Param(p) = trait_ref.self_ty().kind() else { + continue; + }; + if let Some(fun_ty) = self.type_builder.build_closure_type_for_param( + *p, + impl_local_def_id, + self.tcx.mk_args(&[]), + ) { + self.type_builder.register_closure_type_param( + analyze::TypeParam::GenericType(self.type_builder.param_def_id(p), p.index), + fun_ty, + ); + } + } } // Note that we do not expect predicate variables to be generated here From 018444cf53507b5c0a2d28362740178aa374355e Mon Sep 17 00:00:00 2001 From: coeff-aij <175928954+coeff-aij@users.noreply.github.com> Date: Sat, 27 Jun 2026 04:29:46 +0900 Subject: [PATCH 082/142] fix: unboxing ForallPred --- src/chc/unbox.rs | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/src/chc/unbox.rs b/src/chc/unbox.rs index 4f75ecb9..c883cd5b 100644 --- a/src/chc/unbox.rs +++ b/src/chc/unbox.rs @@ -66,7 +66,7 @@ fn unbox_pred(pred: Pred) -> Pred { Pred::Var(pred) => Pred::Var(pred), Pred::Matcher(pred) => unbox_matcher_pred(pred), Pred::UserDefined(pred) => Pred::UserDefined(pred), - Pred::ForallPred(pred) => Pred::ForallPred(pred), + Pred::ForallPred(pred) => Pred::ForallPred(unbox_forall_pred_var_def(pred)), } } @@ -195,10 +195,12 @@ fn unbox_user_defined_pred_def(user_defined_pred_def: UserDefinedPredDef) -> Use } fn unbox_forall_pred_var_def(pred: ForallPred) -> ForallPred { - let args = pred.type_parameters.into_iter().map(unbox_sort).collect(); + let type_parameters = pred.type_parameters.into_iter().map(unbox_sort).collect(); + let params = pred.params.into_iter().map(unbox_sort).collect(); ForallPred { - type_parameters: args, - ..pred + inner: pred.inner, + type_parameters, + params, } } From 2458b2e9f705ec235f23a95b05d4f92c3d1231c3 Mon Sep 17 00:00:00 2001 From: coeff-aij <175928954+coeff-aij@users.noreply.github.com> Date: Sat, 27 Jun 2026 04:30:30 +0900 Subject: [PATCH 083/142] fix: replace commas and whitespaces contained in `Map` as `Map` for user-defined predicate names --- src/chc.rs | 14 +++++++++++++- 1 file changed, 13 insertions(+), 1 deletion(-) diff --git a/src/chc.rs b/src/chc.rs index f0e87b40..666f59a1 100644 --- a/src/chc.rs +++ b/src/chc.rs @@ -1199,7 +1199,19 @@ pub struct UserDefinedPred { impl std::fmt::Display for UserDefinedPred { fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result { - self.inner.fmt(f) + // SMT2 identifiers produced by Thrust come from Rust names (e.g. + // `Map_step`), which the backed solver rejects because + // `,` and ` ` are not allowed inside an identifier. Sanitize at the + // display boundary so the existing human-readable naming convention + // is preserved (`Map_step` → `Map_step`). + for c in self.inner.chars() { + match c { + ',' => f.write_str("-")?, + ' ' | '\t' | '\n' | '\r' => {} + c => f.write_str(c.encode_utf8(&mut [0; 4]))?, + } + } + Ok(()) } } From 9c89768d29051cf4fb424f566111f299f9ff893d Mon Sep 17 00:00:00 2001 From: coeff-aij <175928954+coeff-aij@users.noreply.github.com> Date: Sat, 27 Jun 2026 15:11:11 +0900 Subject: [PATCH 084/142] fix(annot_fn): wrap closure receiver in Mut/Box for FnMut/Fn --- src/analyze/annot_fn.rs | 31 +++++++++++++++++++++++++++++-- 1 file changed, 29 insertions(+), 2 deletions(-) diff --git a/src/analyze/annot_fn.rs b/src/analyze/annot_fn.rs index 89e7fb66..dd79027b 100644 --- a/src/analyze/annot_fn.rs +++ b/src/analyze/annot_fn.rs @@ -425,6 +425,33 @@ impl<'a, 'tcx> AnnotFnTranslator<'a, 'tcx> { } } + /// Wraps a closure receiver term to match the closure's "self" type. + /// + /// For `FnOnce` the receiver is the closure value itself, so the term is returned + /// unchanged. For `Fn` the receiver is `&T` (sort `Box`), and for `FnMut` it is + /// `&mut T` (sort `Mut`); the term is wrapped accordingly so that it matches + /// the sort expected by the registered pre/post forall predicates. + fn wrap_closure_receiver( + &self, + receiver: &'tcx rustc_hir::Expr<'tcx>, + fn_ty: &rty::FunctionType, + ) -> chc::Term { + let receiver_term = self.to_term(receiver); + let first_param = &fn_ty.params[rty::FunctionParamIdx::from_usize(0)]; + match first_param.ty.as_pointer() { + Some(p) if p.is_mut() => chc::Term::mut_(receiver_term.clone(), receiver_term), + Some(p) + if matches!( + p.kind, + rty::PointerKind::Own | rty::PointerKind::Ref(rty::RefKind::Immut) + ) => + { + chc::Term::box_(receiver_term) + } + _ => receiver_term, + } + } + /// The values of a closure's parameters: the closure's first (RustCall) parameter is its /// environment, which is the closure value itself, followed by the logical arguments. fn translate_closure_precondition( @@ -450,7 +477,7 @@ impl<'a, 'tcx> AnnotFnTranslator<'a, 'tcx> { "closure precondition arity mismatch: closure takes {} argument(s)", fn_ty.params.len() - 1 ); - let param_args: Vec<_> = std::iter::once(self.to_term(receiver)) + let param_args: Vec<_> = std::iter::once(self.wrap_closure_receiver(receiver, &fn_ty)) .chain(logical_args) .collect(); FormulaOrTerm::Formula(fn_ty.precondition_formula(¶m_args)) @@ -480,7 +507,7 @@ impl<'a, 'tcx> AnnotFnTranslator<'a, 'tcx> { "closure postcondition arity mismatch: closure takes {} argument(s)", fn_ty.params.len() - 1 ); - let param_args: Vec<_> = std::iter::once(self.to_term(receiver)) + let param_args: Vec<_> = std::iter::once(self.wrap_closure_receiver(receiver, &fn_ty)) .chain(logical_args) .collect(); let result = self.to_term(result); From 6f870e0273e03acec5de191f495ca38068b5257d Mon Sep 17 00:00:00 2001 From: coeff-aij <175928954+coeff-aij@users.noreply.github.com> Date: Sat, 27 Jun 2026 17:58:39 +0900 Subject: [PATCH 085/142] fix(annot_fn): use owner fn's typing env for predicate resolution --- src/analyze/annot_fn.rs | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/src/analyze/annot_fn.rs b/src/analyze/annot_fn.rs index dd79027b..edd991e3 100644 --- a/src/analyze/annot_fn.rs +++ b/src/analyze/annot_fn.rs @@ -907,7 +907,10 @@ impl<'a, 'tcx> AnnotFnTranslator<'a, 'tcx> { .next() .is_some() { - let typing_env = mir_ty::TypingEnv::fully_monomorphized(); + let typing_env = mir_ty::TypingEnv::post_analysis( + self.tcx, + self.type_builder.owner_fn_id(), + ); let generic_args = self.typeck.node_args(func_expr.hir_id); tracing::debug!( lhs = ?def_id, From 7947594347ee6546f05242da34d0a1200df33bbf Mon Sep 17 00:00:00 2001 From: coeff-aij <175928954+coeff-aij@users.noreply.github.com> Date: Sat, 27 Jun 2026 19:32:08 +0900 Subject: [PATCH 086/142] refactor(analyze): use struct fields and local_idx for TypeParam::GenericType --- src/analyze.rs | 9 ++++++++- src/analyze/annot_fn.rs | 8 ++++---- src/analyze/basic_block.rs | 8 ++++---- src/analyze/local_def.rs | 13 ++++++++----- src/refine/template.rs | 23 +++++++++++++++++------ 5 files changed, 41 insertions(+), 20 deletions(-) diff --git a/src/analyze.rs b/src/analyze.rs index 59b7ff4d..e2820854 100644 --- a/src/analyze.rs +++ b/src/analyze.rs @@ -215,7 +215,14 @@ pub type TypeParamMap<'tcx> = HashMap; #[derive(Eq, PartialEq, Hash, Debug, Clone)] pub enum TypeParam { - GenericType(DefId, u32), + /// A type parameter identified by its declaration def_id and its + /// **local** index within the declaring item (i.e. lifetime and const + /// parameters are skipped). Using the local index lets monomorphization + /// substitute it with the actual generic argument at the same position. + GenericType { + param_def_id: DefId, + local_idx: u32, + }, AssocType(DefId, Vec>), } diff --git a/src/analyze/annot_fn.rs b/src/analyze/annot_fn.rs index edd991e3..6af59a81 100644 --- a/src/analyze/annot_fn.rs +++ b/src/analyze/annot_fn.rs @@ -390,10 +390,10 @@ impl<'a, 'tcx> AnnotFnTranslator<'a, 'tcx> { ); if let Some(closure_fun_ty) = closure_fun_ty.clone() { self.type_builder.register_closure_type_param( - analyze::TypeParam::GenericType( - self.type_builder.param_def_id(ty), - ty.index, - ), + analyze::TypeParam::GenericType { + param_def_id: self.type_builder.param_def_id(ty), + local_idx: self.type_builder.param_local_idx(ty), + }, closure_fun_ty, ); }; diff --git a/src/analyze/basic_block.rs b/src/analyze/basic_block.rs index a2042a34..a49b38e1 100644 --- a/src/analyze/basic_block.rs +++ b/src/analyze/basic_block.rs @@ -855,10 +855,10 @@ impl<'tcx, 'ctx> Analyzer<'tcx, 'ctx> { let parent_args = self.tcx.mk_args(&closure_args[..parent_count]); ResolvedCallable::Closure(*closure_def_id, parent_args) } - mir_ty::TyKind::Param(ty) => ResolvedCallable::Generic(TypeParam::GenericType( - self.type_builder.param_def_id(ty), - ty.index, - )), + mir_ty::TyKind::Param(ty) => ResolvedCallable::Generic(TypeParam::GenericType { + param_def_id: self.type_builder.param_def_id(ty), + local_idx: self.type_builder.param_local_idx(ty), + }), kind => { panic!("expected closure arg for fn trait, got: {kind:?}"); } diff --git a/src/analyze/local_def.rs b/src/analyze/local_def.rs index 8269b656..2e95f59c 100644 --- a/src/analyze/local_def.rs +++ b/src/analyze/local_def.rs @@ -423,10 +423,10 @@ impl<'tcx, 'ctx> Analyzer<'tcx, 'ctx> { self.tcx.mk_args(&[]), ) { self.type_builder.register_closure_type_param( - analyze::TypeParam::GenericType( - self.type_builder.param_def_id(¶m_ty), - param_ty.index, - ), + analyze::TypeParam::GenericType { + param_def_id: self.type_builder.param_def_id(¶m_ty), + local_idx: self.type_builder.param_local_idx(¶m_ty), + }, fun_ty, ); } @@ -487,7 +487,10 @@ impl<'tcx, 'ctx> Analyzer<'tcx, 'ctx> { self.tcx.mk_args(&[]), ) { self.type_builder.register_closure_type_param( - analyze::TypeParam::GenericType(self.type_builder.param_def_id(p), p.index), + analyze::TypeParam::GenericType { + param_def_id: self.type_builder.param_def_id(p), + local_idx: self.type_builder.param_local_idx(p), + }, fun_ty, ); } diff --git a/src/refine/template.rs b/src/refine/template.rs index b4bd65f5..55dc8bac 100644 --- a/src/refine/template.rs +++ b/src/refine/template.rs @@ -135,6 +135,17 @@ impl<'tcx> TypeBuilder<'tcx> { generics.param_at(ty.index as usize, self.tcx).def_id } + /// Returns the local index of a type parameter within the declaring item, + /// skipping lifetime and const parameters. This is the position used for + /// monomorphization of generic arguments. + pub fn param_local_idx(&self, ty: &mir_ty::ParamTy) -> u32 { + let idx = self + .param_idx_mapping + .get(&ty.index) + .expect("unknown type param idx"); + u32::from(*idx) + } + fn translate_param_type(&self, ty: &mir_ty::ParamTy) -> rty::Type { // FIXME: // `__ThrustSelf` is currently treated as a distinct `ParamTy` from `Self`, @@ -157,17 +168,17 @@ impl<'tcx> TypeBuilder<'tcx> { tracing::debug!("replace {ty:?} with {self_ty:?}."); return self.translate_param_type(&self_ty); } - let param_local_idx = *self - .param_idx_mapping - .get(&ty.index) - .expect("unknown type param idx"); + let param_local_idx = self.param_local_idx(ty); let param_def_id = self.param_def_id(ty); tracing::debug!("translating ParamTy {ty:?} (decl={param_def_id:?})..."); let mut type_params = self.type_params.borrow_mut(); let forall_sort_idx = type_params - .entry(TypeParam::GenericType(param_def_id, ty.index)) + .entry(TypeParam::GenericType { + param_def_id, + local_idx: param_local_idx, + }) .or_insert_with(|| { let idx = self.system.borrow_mut().new_forall_sort(); tracing::debug!( @@ -178,7 +189,7 @@ impl<'tcx> TypeBuilder<'tcx> { ); idx }); - rty::ParamType::new(param_local_idx, *forall_sort_idx).into() + rty::ParamType::new(rty::TypeParamIdx::from(param_local_idx), *forall_sort_idx).into() } fn translate_alias_type(&self, ty: &mir_ty::AliasTy<'tcx>) -> rty::Type { From d06c11557e9581f5ff476997eea2c213f8ba280e Mon Sep 17 00:00:00 2001 From: coeff-aij <175928954+coeff-aij@users.noreply.github.com> Date: Sat, 27 Jun 2026 19:33:15 +0900 Subject: [PATCH 087/142] feat(chc): substitute ForallSort in ADT monomorphization via resolver closure --- src/chc.rs | 31 ++++++++++++++++++++++++------- src/chc/format_context.rs | 7 +++++-- src/chc/unbox.rs | 2 ++ 3 files changed, 31 insertions(+), 9 deletions(-) diff --git a/src/chc.rs b/src/chc.rs index 666f59a1..3eb89f47 100644 --- a/src/chc.rs +++ b/src/chc.rs @@ -339,23 +339,31 @@ impl Sort { } } - pub fn instantiate_params(&mut self, args: &[Sort]) { + pub fn instantiate_params(&mut self, args: &[Sort], forall_sort_resolver: &F) + where + F: Fn(crate::chc::ForallSortIdx) -> Option, + { match self { Sort::Param(i) => *self = args[*i].clone(), - Sort::Box(s) => s.instantiate_params(args), - Sort::Mut(s) => s.instantiate_params(args), + Sort::Forall(idx) => { + if let Some(local_idx) = forall_sort_resolver(*idx) { + *self = args[local_idx].clone(); + } + } + Sort::Box(s) => s.instantiate_params(args, forall_sort_resolver), + Sort::Mut(s) => s.instantiate_params(args, forall_sort_resolver), Sort::Tuple(ss) => { for s in ss { - s.instantiate_params(args); + s.instantiate_params(args, forall_sort_resolver); } } Sort::Array(s1, s2) => { - s1.instantiate_params(args); - s2.instantiate_params(args); + s1.instantiate_params(args, forall_sort_resolver); + s2.instantiate_params(args, forall_sort_resolver); } Sort::Datatype(sort) => { for s in &mut sort.args { - s.instantiate_params(args); + s.instantiate_params(args, forall_sort_resolver); } } _ => {} @@ -2188,6 +2196,15 @@ pub struct System { pub pred_vars: IndexVec, pub forall_sorts: Vec, pub num_forall_sort_idx: ForallSortIdx, + /// Reverse map from [`ForallSortIdx`] to the local index of the type + /// parameter it was issued for, populated by the analyzer. Used during + /// datatype monomorphization to substitute `Sort::Forall` placeholders + /// in ADT field types with the corresponding generic argument. + /// + /// Only entries for `analyze::TypeParam::GenericType` are recorded. + /// `analyze::TypeParam::AssocType` forall sorts are intentionally omitted + /// and remain as opaque forall sorts in the SMT output. + pub type_params_reverse: HashMap, forall_pred_vars: HashSet, } diff --git a/src/chc/format_context.rs b/src/chc/format_context.rs index 62d3a07d..d2140e0c 100644 --- a/src/chc/format_context.rs +++ b/src/chc/format_context.rs @@ -235,6 +235,7 @@ fn collect_sorts(system: &chc::System) -> BTreeSet { fn monomorphize_datatype( sort: &chc::DatatypeSort, datatypes: &[chc::Datatype], + forall_sort_resolver: &impl Fn(chc::ForallSortIdx) -> Option, ) -> Option { let datatype = datatypes.iter().find(|d| d.symbol == sort.symbol).unwrap(); if datatype.params == 0 { @@ -254,7 +255,7 @@ fn monomorphize_datatype( .iter() .map(|s| { let mut sel_sort = s.sort.clone(); - sel_sort.instantiate_params(&sort.args); + sel_sort.instantiate_params(&sort.args, forall_sort_resolver); chc::DatatypeSelector { symbol: chc::DatatypeSymbol::new(format!("{}{}", s.symbol, ss)), sort: sel_sort, @@ -270,10 +271,12 @@ fn monomorphize_datatype( impl FormatContext { pub fn from_system(system: &chc::System) -> Self { + let type_params_reverse = system.type_params_reverse.clone(); + let resolver = |idx: chc::ForallSortIdx| type_params_reverse.get(&idx).map(|&i| i as usize); let sorts = collect_sorts(system); let mut datatypes = system.datatypes.clone(); for sort in sorts.iter().flat_map(|s| s.as_datatype()) { - if let Some(mono_datatype) = monomorphize_datatype(sort, &datatypes) { + if let Some(mono_datatype) = monomorphize_datatype(sort, &datatypes, &resolver) { datatypes.push(mono_datatype); } } diff --git a/src/chc/unbox.rs b/src/chc/unbox.rs index c883cd5b..9c9ede64 100644 --- a/src/chc/unbox.rs +++ b/src/chc/unbox.rs @@ -218,6 +218,7 @@ pub fn unbox(system: System) -> System { pred_vars, forall_sorts, num_forall_sort_idx, + type_params_reverse, forall_pred_vars, } = system; let datatypes = datatypes.into_iter().map(unbox_datatype).collect(); @@ -239,6 +240,7 @@ pub fn unbox(system: System) -> System { pred_vars, forall_sorts, num_forall_sort_idx, + type_params_reverse, forall_pred_vars, } } From bff22f12a8751e2781a504a501bb3a3a8207aa3c Mon Sep 17 00:00:00 2001 From: coeff-aij <175928954+coeff-aij@users.noreply.github.com> Date: Sat, 27 Jun 2026 19:34:26 +0900 Subject: [PATCH 088/142] feat(analyze): populate type_params_reverse before solve --- src/analyze.rs | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/src/analyze.rs b/src/analyze.rs index e2820854..533e8330 100644 --- a/src/analyze.rs +++ b/src/analyze.rs @@ -725,6 +725,13 @@ impl<'tcx> Analyzer<'tcx> { } pub fn solve(&mut self) { + let mut reverse = HashMap::new(); + for (tp, &idx) in self.type_params.borrow().iter() { + if let TypeParam::GenericType { local_idx, .. } = tp { + reverse.insert(idx, *local_idx); + } + } + self.system.borrow_mut().type_params_reverse = reverse; if let Err(err) = self.system.borrow().solve() { self.tcx.dcx().err(format!("verification error: {:?}", err)); } From 57d9d4c9edc51d02639a684f73478f778bb19d2e Mon Sep 17 00:00:00 2001 From: coeff-aij <175928954+coeff-aij@users.noreply.github.com> Date: Sun, 28 Jun 2026 00:29:35 +0900 Subject: [PATCH 089/142] feat(chc): add dependencies field to UserDefinedPredDef --- src/chc.rs | 21 +++++++++++++++++++-- src/chc/unbox.rs | 14 ++++++++++++-- 2 files changed, 31 insertions(+), 4 deletions(-) diff --git a/src/chc.rs b/src/chc.rs index 3eb89f47..6af3bdf2 100644 --- a/src/chc.rs +++ b/src/chc.rs @@ -2157,6 +2157,9 @@ pub struct UserDefinedPredDef { symbol: UserDefinedPred, sig: UserDefinedPredSig, body: String, + /// `ForallPred`s referenced from `body`. Populated just before dependency + /// analysis by `System::populate_user_defined_pred_dependencies`. + pub dependencies: HashSet, } pub fn compute_transitive_closure(direct_deps: &HashMap>) -> HashMap> @@ -2234,8 +2237,22 @@ impl System { sig: UserDefinedPredSig, body: String, ) { - self.user_defined_pred_defs - .push(UserDefinedPredDef { symbol, sig, body }) + self.push_pred_define_with_deps(symbol, sig, body, HashSet::new()) + } + + pub fn push_pred_define_with_deps( + &mut self, + symbol: UserDefinedPred, + sig: UserDefinedPredSig, + body: String, + dependencies: HashSet, + ) { + self.user_defined_pred_defs.push(UserDefinedPredDef { + symbol, + sig, + body, + dependencies, + }) } pub fn push_clause(&mut self, clause: Clause) -> Option { diff --git a/src/chc/unbox.rs b/src/chc/unbox.rs index 9c9ede64..2d9377d0 100644 --- a/src/chc/unbox.rs +++ b/src/chc/unbox.rs @@ -186,12 +186,22 @@ fn unbox_datatype(datatype: Datatype) -> Datatype { } fn unbox_user_defined_pred_def(user_defined_pred_def: UserDefinedPredDef) -> UserDefinedPredDef { - let UserDefinedPredDef { symbol, sig, body } = user_defined_pred_def; + let UserDefinedPredDef { + symbol, + sig, + body, + dependencies, + } = user_defined_pred_def; let sig = sig .into_iter() .map(|(name, sort)| (name, unbox_sort(sort))) .collect(); - UserDefinedPredDef { symbol, sig, body } + UserDefinedPredDef { + symbol, + sig, + body, + dependencies, + } } fn unbox_forall_pred_var_def(pred: ForallPred) -> ForallPred { From 0a5b2332f5c65ff6a7348395d637ba01047bee82 Mon Sep 17 00:00:00 2001 From: coeff-aij <175928954+coeff-aij@users.noreply.github.com> Date: Sun, 28 Jun 2026 00:34:25 +0900 Subject: [PATCH 090/142] feat(chc): scan forall pred refs from user predicate bodies --- src/chc.rs | 31 ++++++++++++++++++++++++++++++- src/chc/format_context.rs | 12 ++++++++++-- 2 files changed, 40 insertions(+), 3 deletions(-) diff --git a/src/chc.rs b/src/chc.rs index 6af3bdf2..3e3d0557 100644 --- a/src/chc.rs +++ b/src/chc.rs @@ -2255,6 +2255,34 @@ impl System { }) } + /// Scans every [`UserDefinedPredDef`]'s body for references to registered + /// [`ForallPred`]s and records the matches in `dependencies`. + /// + /// The user-supplied SMT-LIB2 body of a `#[thrust_macros::predicate]` is a + /// raw string and therefore opaque to the analyzer. To still let dependency + /// analysis see transitive `ForallPred` uses, we look for the SMT-LIB2 + /// representation of every registered `ForallPred` as a substring of each + /// body. Must be called after every `ForallPred` has been registered + /// (i.e. after `crate::refine::template` and trait/closure pre/post + /// construction finish) and before [`System::compute_dependency`]. + pub fn populate_user_defined_pred_dependencies(&mut self) { + use crate::chc::format_context::format_forall_pred_name; + + let forall_names: Vec<(ForallPred, String)> = self + .forall_pred_vars + .iter() + .map(|pred| (pred.clone(), format_forall_pred_name(pred))) + .collect(); + + for udpd in &mut self.user_defined_pred_defs { + for (pred, name) in &forall_names { + if udpd.body.contains(name.as_str()) { + udpd.dependencies.insert(pred.clone()); + } + } + } + } + pub fn push_clause(&mut self, clause: Clause) -> Option { if clause.is_nop() { return None; @@ -2339,7 +2367,8 @@ impl System { /// variables /// (see ). pub fn solve(&self) -> Result<(), CheckSatError> { - let system = unbox(self.clone()); + let mut system = unbox(self.clone()); + system.populate_user_defined_pred_dependencies(); if let Ok(file) = std::env::var("THRUST_PRETTY_OUTPUT") { let mut f = std::fs::File::create(file).unwrap(); for (idx, c) in system.clauses.iter_enumerated() { diff --git a/src/chc/format_context.rs b/src/chc/format_context.rs index d2140e0c..503fec33 100644 --- a/src/chc/format_context.rs +++ b/src/chc/format_context.rs @@ -141,6 +141,15 @@ impl<'a> SortSymbols<'a> { } } +/// SMT-LIB2 representation of a [`chc::ForallPred`]'s identifier as it appears +/// in atoms (e.g. `q_completed_8cab…`). Used by the SMT emitter and by +/// [`chc::System::populate_user_defined_pred_dependencies`] to substring-match +/// `ForallPred` references inside user-defined predicate bodies. +pub fn format_forall_pred_name(p: &chc::ForallPred) -> String { + let ss = SortSymbols::new(&p.type_parameters); + format!("{}{}", p.inner, ss) +} + fn builtin_sort_datatype(s: chc::Sort) -> Option { let symbol = SortSymbol::new(&s).to_symbol(); let d = match s { @@ -374,8 +383,7 @@ impl FormatContext { } pub fn forall_pred(&self, p: &chc::ForallPred) -> impl std::fmt::Display { - let ss = SortSymbols::new(&p.type_parameters); - format!("{}{}", p.inner, ss) + format_forall_pred_name(p) } pub fn concat_int_array(&self, elem: &chc::Sort) -> impl std::fmt::Display { From c41997b52df1f054361e35a4a3438561bb215f68 Mon Sep 17 00:00:00 2001 From: coeff-aij <175928954+coeff-aij@users.noreply.github.com> Date: Sun, 28 Jun 2026 00:40:42 +0900 Subject: [PATCH 091/142] feat(chc): integrate user-defined pred deps into dep analysis --- src/chc.rs | 60 ++++++++++++++++++++++++++++++++++++++++++++---------- 1 file changed, 49 insertions(+), 11 deletions(-) diff --git a/src/chc.rs b/src/chc.rs index 3e3d0557..8bb6601a 100644 --- a/src/chc.rs +++ b/src/chc.rs @@ -2189,6 +2189,15 @@ where closure } +/// A thing that can be referenced from a clause body and contribute to the +/// transitive dependency closure: a predicate variable, or a user-defined +/// predicate whose body may call `ForallPred`s. +#[derive(Debug, Clone, Hash, Eq, PartialEq)] +pub enum ExistsDep { + PredVar(PredVarId), + UserDefined(UserDefinedPred), +} + /// A CHC system. #[derive(Debug, Clone, Default)] pub struct System { @@ -2299,20 +2308,21 @@ impl System { .collect() } - fn compute_exists_dependency(clause: &Clause) -> HashSet { + fn compute_exists_dependency(clause: &Clause) -> HashSet { clause .body .iter_atoms() - .filter_map(|atom| match atom.pred { - Pred::Var(id) => Some(id), + .filter_map(|atom| match &atom.pred { + Pred::Var(id) => Some(ExistsDep::PredVar(*id)), + Pred::UserDefined(p) => Some(ExistsDep::UserDefined(p.clone())), _ => None, }) .collect() } fn compute_dependency(&self) -> HashMap> { - let mut exists_deps: HashMap> = HashMap::new(); - let mut forall_deps: HashMap> = HashMap::new(); + let mut exists_deps: HashMap> = HashMap::new(); + let mut forall_deps: HashMap> = HashMap::new(); for (clause_idx, clause) in self.clauses.iter_enumerated() { let Pred::Var(head_id) = clause.head.pred else { @@ -2329,8 +2339,23 @@ impl System { exists ); - exists_deps.entry(head_id).or_default().extend(exists); - forall_deps.entry(head_id).or_default().extend(forall); + let head_dep = ExistsDep::PredVar(head_id); + exists_deps + .entry(head_dep.clone()) + .or_default() + .extend(exists); + forall_deps.entry(head_dep).or_default().extend(forall); + } + // Each `UserDefinedPred`'s body may call `ForallPred`s. We populate + // these lazily via `populate_user_defined_pred_dependencies`; thread + // them into the forall map so transitive propagation sees them. + for udpd in &self.user_defined_pred_defs { + if !udpd.dependencies.is_empty() { + forall_deps + .entry(ExistsDep::UserDefined(udpd.symbol.clone())) + .or_default() + .extend(udpd.dependencies.iter().cloned()); + } } tracing::debug!("direct forall dependencies: {:#?}", forall_deps); tracing::debug!("direct exists dependencies: {:#?}", exists_deps); @@ -2340,11 +2365,24 @@ impl System { let mut propagated_forall_deps = HashMap::new(); - for (pred, reachable_preds) in transitive_exists_deps { - let mut deps = forall_deps.get(&pred).cloned().unwrap_or_default(); + for (dep, reachable) in transitive_exists_deps { + let ExistsDep::PredVar(pred) = dep else { + // Only `PredVar` heads appear as `dep` keys here: a clause + // head is always `Pred::Var` (see the loop above). Other + // variants would only be reachable successors. + continue; + }; + + // Direct ForallPred deps of the head predicate variable (those + // `ForallPred` atoms that appear directly in the clause body of + // `pred`). The transitive-closure result excludes the start node + // itself, so we add this here explicitly. + let mut deps = forall_deps.get(&dep).cloned().unwrap_or_default(); - for reachable in reachable_preds { - if let Some(foralls) = forall_deps.get(&reachable) { + // ForallPred deps contributed by each reachable successor + // (`PredVar` or `UserDefinedPred`). + for r in &reachable { + if let Some(foralls) = forall_deps.get(r) { deps.extend(foralls.iter().cloned()); } } From aa22e2c1c8d4f1991bae6490ba65b4e06b749535 Mon Sep 17 00:00:00 2001 From: coeff-aij <175928954+coeff-aij@users.noreply.github.com> Date: Sun, 28 Jun 2026 01:58:27 +0900 Subject: [PATCH 092/142] fix(annot_fn): avoid double-wrapping already-Mut closure receivers --- src/analyze/annot_fn.rs | 29 +++++++++++++++++++++++------ 1 file changed, 23 insertions(+), 6 deletions(-) diff --git a/src/analyze/annot_fn.rs b/src/analyze/annot_fn.rs index 6af59a81..dc17723d 100644 --- a/src/analyze/annot_fn.rs +++ b/src/analyze/annot_fn.rs @@ -431,12 +431,27 @@ impl<'a, 'tcx> AnnotFnTranslator<'a, 'tcx> { /// unchanged. For `Fn` the receiver is `&T` (sort `Box`), and for `FnMut` it is /// `&mut T` (sort `Mut`); the term is wrapped accordingly so that it matches /// the sort expected by the registered pre/post forall predicates. + /// + /// `Box` sorts are stripped by the `unbox` pass (`src/chc/unbox.rs`) before the + /// CHC reaches the solver, so this function does not need a special case for + /// `model::Box` receivers. For `model::Mut` (constructed by `Mut::new(..)` + /// in the annotation) the term is returned as-is, since its sort already matches + /// the env expected by the forall predicate; wrapping it again would produce a + /// `Mut>` value and cause a sort mismatch at the call site. fn wrap_closure_receiver( &self, receiver: &'tcx rustc_hir::Expr<'tcx>, + receiver_ty: mir_ty::Ty<'tcx>, fn_ty: &rty::FunctionType, ) -> chc::Term { let receiver_term = self.to_term(receiver); + + if let mir_ty::TyKind::Adt(adt, _) = receiver_ty.kind() { + if Some(adt.did()) == self.def_ids.mut_model() { + return receiver_term; + } + } + let first_param = &fn_ty.params[rty::FunctionParamIdx::from_usize(0)]; match first_param.ty.as_pointer() { Some(p) if p.is_mut() => chc::Term::mut_(receiver_term.clone(), receiver_term), @@ -477,9 +492,10 @@ impl<'a, 'tcx> AnnotFnTranslator<'a, 'tcx> { "closure precondition arity mismatch: closure takes {} argument(s)", fn_ty.params.len() - 1 ); - let param_args: Vec<_> = std::iter::once(self.wrap_closure_receiver(receiver, &fn_ty)) - .chain(logical_args) - .collect(); + let param_args: Vec<_> = + std::iter::once(self.wrap_closure_receiver(receiver, receiver_ty, &fn_ty)) + .chain(logical_args) + .collect(); FormulaOrTerm::Formula(fn_ty.precondition_formula(¶m_args)) } @@ -507,9 +523,10 @@ impl<'a, 'tcx> AnnotFnTranslator<'a, 'tcx> { "closure postcondition arity mismatch: closure takes {} argument(s)", fn_ty.params.len() - 1 ); - let param_args: Vec<_> = std::iter::once(self.wrap_closure_receiver(receiver, &fn_ty)) - .chain(logical_args) - .collect(); + let param_args: Vec<_> = + std::iter::once(self.wrap_closure_receiver(receiver, receiver_ty, &fn_ty)) + .chain(logical_args) + .collect(); let result = self.to_term(result); FormulaOrTerm::Formula(fn_ty.postcondition_formula(¶m_args, result)) } From e2a2d43a76532dcea628447b8c57ad10a89d527b Mon Sep 17 00:00:00 2001 From: coeff-aij <175928954+coeff-aij@users.noreply.github.com> Date: Sun, 16 Aug 2026 19:09:59 +0900 Subject: [PATCH 093/142] tests: configure extended CoAR solver --- tests/ui/fail/adt_enum_field.rs | 1 + tests/ui/fail/adt_poly_fn_mono.rs | 1 + tests/ui/fail/adt_poly_ref.rs | 1 + tests/ui/fail/adt_variant_without_params.rs | 1 + tests/ui/fail/annot_mut_term_formula_fn_poly.rs | 1 + tests/ui/fail/annot_preds.rs | 1 + tests/ui/fail/closure_captures.rs | 2 +- tests/ui/fail/closure_captures_fn_once.rs | 2 +- tests/ui/fail/closure_ensures_only.rs | 2 +- tests/ui/fail/closure_model_preserve.rs | 2 +- tests/ui/fail/closure_mut_capture_pre_post.rs | 2 +- tests/ui/fail/closure_param.rs | 2 +- tests/ui/fail/closure_param_weaken_1.rs | 2 +- tests/ui/fail/closure_param_weaken_2.rs | 2 +- tests/ui/fail/closure_param_weaken_3.rs | 2 +- tests/ui/fail/closure_postcondition.rs | 2 +- tests/ui/fail/closure_receiver_mut_model.rs | 2 +- tests/ui/fail/closure_receiver_mut_model_byval.rs | 2 +- tests/ui/fail/closure_ref_mut_pre_post.rs | 2 +- tests/ui/fail/closure_ref_pre_post.rs | 2 +- tests/ui/fail/closure_requires_ensures.rs | 2 +- tests/ui/fail/closure_requires_only.rs | 2 +- tests/ui/fail/fn_poly.rs | 1 + tests/ui/fail/fn_poly_annot.rs | 1 + tests/ui/fail/fn_poly_annot_2.rs | 2 +- tests/ui/fail/fn_poly_annot_complex.rs | 1 + tests/ui/fail/fn_poly_annot_multi_inst.rs | 1 + tests/ui/fail/fn_poly_annot_nested.rs | 1 + tests/ui/fail/fn_poly_annot_recursive.rs | 2 +- tests/ui/fail/fn_poly_annot_ref.rs | 1 + tests/ui/fail/fn_poly_annot_singleton.rs | 2 +- tests/ui/fail/fn_poly_annot_stronger.rs | 2 +- tests/ui/fail/fn_poly_multiple_calls.rs | 1 + tests/ui/fail/fn_poly_mut_ref.rs | 1 + tests/ui/fail/fn_poly_param_order.rs | 1 + tests/ui/fail/fn_poly_recursive.rs | 2 +- tests/ui/fail/fn_poly_ref.rs | 1 + tests/ui/fail/fn_poly_ref_ord.rs | 1 + tests/ui/fail/fn_poly_unused_param.rs | 1 + tests/ui/fail/issue_108.rs | 2 +- tests/ui/fail/iterators/annot_range_loop.rs | 2 +- tests/ui/fail/iterators/annot_range_next.rs | 2 +- tests/ui/fail/iterators/fixed_filter_loop_none.rs | 1 + tests/ui/fail/iterators/fixed_filter_next_some.rs | 1 + tests/ui/fail/iterators/range.rs | 2 +- tests/ui/fail/loop_invariant_fn_param_closure.rs | 2 +- tests/ui/fail/loop_invariant_generic.rs | 2 +- tests/ui/fail/loop_invariant_trait.rs | 2 +- tests/ui/fail/option_inc.rs | 1 + tests/ui/fail/option_loop.rs | 1 + tests/ui/fail/option_map.rs | 2 +- tests/ui/fail/option_mut.rs | 1 + tests/ui/fail/option_unwrap_or_else.rs | 2 +- tests/ui/fail/refine_param_generic_adt.rs | 1 + tests/ui/fail/refine_param_nested_binder.rs | 1 + tests/ui/fail/refine_param_path_qualified.rs | 1 + tests/ui/fail/refine_sig_generic_adt.rs | 1 + tests/ui/fail/result_mut.rs | 1 + tests/ui/fail/result_struct.rs | 1 + tests/ui/fail/slice_first_mut.rs | 1 + tests/ui/fail/slice_last_mut.rs | 1 + tests/ui/fail/slice_methods.rs | 1 + tests/ui/fail/slice_methods_mut.rs | 1 + tests/ui/fail/trait_assoc_type_spec.rs | 2 +- tests/ui/fail/vec_2.rs | 1 + tests/ui/pass/adt_enum_field.rs | 1 + tests/ui/pass/adt_poly_fn_mono.rs | 1 + tests/ui/pass/adt_poly_ref.rs | 1 + tests/ui/pass/adt_variant_without_params.rs | 1 + tests/ui/pass/annot_mut_term_formula_fn_poly.rs | 1 + tests/ui/pass/annot_preds.rs | 1 + tests/ui/pass/closure_captures.rs | 2 +- tests/ui/pass/closure_captures_fn_once.rs | 2 +- tests/ui/pass/closure_ensures_only.rs | 2 +- tests/ui/pass/closure_model_preserve.rs | 2 +- tests/ui/pass/closure_mut_capture_pre_post.rs | 2 +- tests/ui/pass/closure_param.rs | 2 +- tests/ui/pass/closure_param_weaken_1.rs | 2 +- tests/ui/pass/closure_param_weaken_2.rs | 2 +- tests/ui/pass/closure_param_weaken_3.rs | 2 +- tests/ui/pass/closure_postcondition.rs | 2 +- tests/ui/pass/closure_postcondition_generic.rs | 2 +- tests/ui/pass/closure_receiver_mut_model.rs | 2 +- tests/ui/pass/closure_receiver_mut_model_byval.rs | 2 +- tests/ui/pass/closure_ref_mut_pre_post.rs | 2 +- tests/ui/pass/closure_ref_pre_post.rs | 2 +- tests/ui/pass/closure_requires_ensures.rs | 2 +- tests/ui/pass/closure_requires_only.rs | 2 +- tests/ui/pass/fn_poly.rs | 1 + tests/ui/pass/fn_poly_annot.rs | 1 + tests/ui/pass/fn_poly_annot_2.rs | 2 +- tests/ui/pass/fn_poly_annot_complex.rs | 2 +- tests/ui/pass/fn_poly_annot_multi_inst.rs | 1 + tests/ui/pass/fn_poly_annot_nested.rs | 1 + tests/ui/pass/fn_poly_annot_recursive.rs | 2 +- tests/ui/pass/fn_poly_annot_ref.rs | 1 + tests/ui/pass/fn_poly_annot_singleton.rs | 2 +- tests/ui/pass/fn_poly_annot_stronger.rs | 2 +- tests/ui/pass/fn_poly_multiple_calls.rs | 1 + tests/ui/pass/fn_poly_mut_ref.rs | 1 + tests/ui/pass/fn_poly_param_order.rs | 1 + tests/ui/pass/fn_poly_recursive.rs | 2 +- tests/ui/pass/fn_poly_ref.rs | 1 + tests/ui/pass/fn_poly_ref_ord.rs | 1 + tests/ui/pass/fn_poly_unused_param.rs | 1 + tests/ui/pass/issue_108.rs | 2 +- tests/ui/pass/iterators/annot_range_loop.rs | 2 +- tests/ui/pass/iterators/annot_range_next.rs | 2 +- tests/ui/pass/iterators/fixed_filter_loop_none.rs | 1 + tests/ui/pass/iterators/fixed_filter_next_some.rs | 1 + tests/ui/pass/iterators/range.rs | 2 +- tests/ui/pass/loop_invariant_fn_param_closure.rs | 2 +- tests/ui/pass/loop_invariant_generic.rs | 2 +- tests/ui/pass/loop_invariant_generic_closure.rs | 2 +- tests/ui/pass/loop_invariant_trait.rs | 2 +- tests/ui/pass/option_inc.rs | 1 + tests/ui/pass/option_loop.rs | 1 + tests/ui/pass/option_map.rs | 2 +- tests/ui/pass/option_mut.rs | 1 + tests/ui/pass/option_unwrap_or_else.rs | 2 +- tests/ui/pass/refine_param_generic_adt.rs | 1 + tests/ui/pass/refine_param_nested_binder.rs | 1 + tests/ui/pass/refine_param_path_qualified.rs | 1 + tests/ui/pass/refine_sig_generic_adt.rs | 1 + tests/ui/pass/result_mut.rs | 1 + tests/ui/pass/result_struct.rs | 1 + tests/ui/pass/slice_first_mut.rs | 1 + tests/ui/pass/slice_last_mut.rs | 1 + tests/ui/pass/slice_methods.rs | 1 + tests/ui/pass/slice_methods_mut.rs | 1 + tests/ui/pass/trait_assoc_type_spec.rs | 2 +- tests/ui/pass/traits/annot_simple_loop_self.rs | 2 +- tests/ui/pass/traits/fold.rs | 2 +- tests/ui/pass/traits/loop_unbound.rs | 2 +- tests/ui/pass/traits/multi_params.rs | 2 +- tests/ui/pass/traits/option_map.rs | 2 +- tests/ui/pass/traits/simple_loop.rs | 2 +- tests/ui/pass/traits/simple_loop_2int.rs | 2 +- tests/ui/pass/traits/simple_loop_call.rs | 2 +- tests/ui/pass/traits/simple_loop_call_multi.rs | 2 +- tests/ui/pass/traits/simple_loop_self.rs | 2 +- tests/ui/pass/traits/simple_loop_self_mut.rs | 2 +- tests/ui/pass/traits/two_loops.rs | 2 +- tests/ui/pass/vec_2.rs | 1 + 144 files changed, 144 insertions(+), 77 deletions(-) diff --git a/tests/ui/fail/adt_enum_field.rs b/tests/ui/fail/adt_enum_field.rs index 40f909b5..366b8d4b 100644 --- a/tests/ui/fail/adt_enum_field.rs +++ b/tests/ui/fail/adt_enum_field.rs @@ -1,5 +1,6 @@ //@error-in-other-file: Unsat //@compile-flags: -Adead_code -C debug-assertions=off +//@rustc-env: THRUST_SOLVER=tests/thrust-pcsat-wrapper COAR_IMAGE=coar:latest struct Wrap { o: Option, diff --git a/tests/ui/fail/adt_poly_fn_mono.rs b/tests/ui/fail/adt_poly_fn_mono.rs index f93fd993..f684a0a7 100644 --- a/tests/ui/fail/adt_poly_fn_mono.rs +++ b/tests/ui/fail/adt_poly_fn_mono.rs @@ -1,5 +1,6 @@ //@error-in-other-file: Unsat //@compile-flags: -C debug-assertions=off +//@rustc-env: THRUST_SOLVER=tests/thrust-pcsat-wrapper COAR_IMAGE=coar:latest pub enum X { A(T), diff --git a/tests/ui/fail/adt_poly_ref.rs b/tests/ui/fail/adt_poly_ref.rs index 8c42d4b2..a1071157 100644 --- a/tests/ui/fail/adt_poly_ref.rs +++ b/tests/ui/fail/adt_poly_ref.rs @@ -1,5 +1,6 @@ //@error-in-other-file: Unsat //@compile-flags: -C debug-assertions=off +//@rustc-env: THRUST_SOLVER=tests/thrust-pcsat-wrapper COAR_IMAGE=coar:latest enum X<'a, T> { A(&'a T), diff --git a/tests/ui/fail/adt_variant_without_params.rs b/tests/ui/fail/adt_variant_without_params.rs index bd8c5005..1d915e12 100644 --- a/tests/ui/fail/adt_variant_without_params.rs +++ b/tests/ui/fail/adt_variant_without_params.rs @@ -1,3 +1,4 @@ +//@rustc-env: THRUST_SOLVER=tests/thrust-pcsat-wrapper COAR_IMAGE=coar:latest //@error-in-other-file: Unsat enum X { diff --git a/tests/ui/fail/annot_mut_term_formula_fn_poly.rs b/tests/ui/fail/annot_mut_term_formula_fn_poly.rs index e87f5e3d..c9d9214b 100644 --- a/tests/ui/fail/annot_mut_term_formula_fn_poly.rs +++ b/tests/ui/fail/annot_mut_term_formula_fn_poly.rs @@ -1,4 +1,5 @@ //@error-in-other-file: Unsat +//@rustc-env: THRUST_SOLVER=tests/thrust-pcsat-wrapper COAR_IMAGE=coar:latest #[allow(unused_variables)] #[thrust::formula_fn] diff --git a/tests/ui/fail/annot_preds.rs b/tests/ui/fail/annot_preds.rs index 725e23ba..3d18f096 100644 --- a/tests/ui/fail/annot_preds.rs +++ b/tests/ui/fail/annot_preds.rs @@ -1,5 +1,6 @@ //@error-in-other-file: Unsat //@compile-flags: -Adead_code -C debug-assertions=off +//@rustc-env: THRUST_SOLVER=tests/thrust-pcsat-wrapper COAR_IMAGE=coar:latest #[thrust::predicate] fn is_double(x: thrust_models::model::Int, doubled_x: thrust_models::model::Int) -> bool { diff --git a/tests/ui/fail/closure_captures.rs b/tests/ui/fail/closure_captures.rs index 0da513e9..7401a365 100644 --- a/tests/ui/fail/closure_captures.rs +++ b/tests/ui/fail/closure_captures.rs @@ -1,6 +1,6 @@ //@error-in-other-file: Unsat //@compile-flags: -C debug-assertions=off - +//@rustc-env: THRUST_SOLVER=tests/thrust-pcsat-wrapper COAR_IMAGE=coar:latest #[thrust_macros::requires(thrust_macros::pre!(f(x)))] #[thrust_macros::ensures(thrust_macros::post!(f(x), result))] fn apply i32>(x: i32, f: F) -> i32 { diff --git a/tests/ui/fail/closure_captures_fn_once.rs b/tests/ui/fail/closure_captures_fn_once.rs index bd2efe00..0a0a64e0 100644 --- a/tests/ui/fail/closure_captures_fn_once.rs +++ b/tests/ui/fail/closure_captures_fn_once.rs @@ -1,6 +1,6 @@ //@error-in-other-file: Unsat //@compile-flags: -C debug-assertions=off - +//@rustc-env: THRUST_SOLVER=tests/thrust-pcsat-wrapper COAR_IMAGE=coar:latest #[thrust_macros::requires(thrust_macros::pre!(f(x)))] #[thrust_macros::ensures(thrust_macros::post!(f(x), result))] fn apply i32>(x: i32, f: F) -> i32 { diff --git a/tests/ui/fail/closure_ensures_only.rs b/tests/ui/fail/closure_ensures_only.rs index 7ec22b2c..75066319 100644 --- a/tests/ui/fail/closure_ensures_only.rs +++ b/tests/ui/fail/closure_ensures_only.rs @@ -1,6 +1,6 @@ //@error-in-other-file: Unsat //@compile-flags: -C debug-assertions=off - +//@rustc-env: THRUST_SOLVER=tests/thrust-pcsat-wrapper COAR_IMAGE=coar:latest // The declared postcondition `result > x` hides the body's exact result, so `r == 4` // is not provable. Were the postcondition inferred instead, it would be exact and the // assertion would hold. diff --git a/tests/ui/fail/closure_model_preserve.rs b/tests/ui/fail/closure_model_preserve.rs index 2a91eecf..88c163ed 100644 --- a/tests/ui/fail/closure_model_preserve.rs +++ b/tests/ui/fail/closure_model_preserve.rs @@ -1,6 +1,6 @@ //@error-in-other-file: Unsat //@compile-flags: -C debug-assertions=off - +//@rustc-env: THRUST_SOLVER=tests/thrust-pcsat-wrapper COAR_IMAGE=coar:latest #[thrust_macros::ensures(result == f)] fn call i32>(mut f: F) -> F { f(); diff --git a/tests/ui/fail/closure_mut_capture_pre_post.rs b/tests/ui/fail/closure_mut_capture_pre_post.rs index 4db0b583..83fcbbe9 100644 --- a/tests/ui/fail/closure_mut_capture_pre_post.rs +++ b/tests/ui/fail/closure_mut_capture_pre_post.rs @@ -1,6 +1,6 @@ //@error-in-other-file: Unsat //@compile-flags: -C debug-assertions=off - +//@rustc-env: THRUST_SOLVER=tests/thrust-pcsat-wrapper COAR_IMAGE=coar:latest #[thrust_macros::requires(thrust_macros::pre!(f()))] #[thrust_macros::ensures(thrust_macros::post!(f(), result))] fn call i64>(mut f: F) -> i64 { diff --git a/tests/ui/fail/closure_param.rs b/tests/ui/fail/closure_param.rs index bd9dc23b..00863429 100644 --- a/tests/ui/fail/closure_param.rs +++ b/tests/ui/fail/closure_param.rs @@ -1,6 +1,6 @@ //@error-in-other-file: Unsat //@compile-flags: -C debug-assertions=off - +//@rustc-env: THRUST_SOLVER=tests/thrust-pcsat-wrapper COAR_IMAGE=coar:latest fn take_fn T>(f: F) -> T { f(42) } diff --git a/tests/ui/fail/closure_param_weaken_1.rs b/tests/ui/fail/closure_param_weaken_1.rs index cbf09ef2..6d59dd51 100644 --- a/tests/ui/fail/closure_param_weaken_1.rs +++ b/tests/ui/fail/closure_param_weaken_1.rs @@ -1,6 +1,6 @@ //@error-in-other-file: Unsat //@compile-flags: -C debug-assertions=off - +//@rustc-env: THRUST_SOLVER=tests/thrust-pcsat-wrapper COAR_IMAGE=coar:latest fn apply(f: F) -> i32 where F: FnOnce(i32) -> i32, diff --git a/tests/ui/fail/closure_param_weaken_2.rs b/tests/ui/fail/closure_param_weaken_2.rs index 51f682bf..a074512e 100644 --- a/tests/ui/fail/closure_param_weaken_2.rs +++ b/tests/ui/fail/closure_param_weaken_2.rs @@ -1,6 +1,6 @@ //@error-in-other-file: Unsat //@compile-flags: -C debug-assertions=off - +//@rustc-env: THRUST_SOLVER=tests/thrust-pcsat-wrapper COAR_IMAGE=coar:latest fn apply(mut f: F) -> i32 where F: FnMut(i32) -> i32, diff --git a/tests/ui/fail/closure_param_weaken_3.rs b/tests/ui/fail/closure_param_weaken_3.rs index 606e54b4..69fd9791 100644 --- a/tests/ui/fail/closure_param_weaken_3.rs +++ b/tests/ui/fail/closure_param_weaken_3.rs @@ -1,6 +1,6 @@ //@error-in-other-file: Unsat //@compile-flags: -C debug-assertions=off - +//@rustc-env: THRUST_SOLVER=tests/thrust-pcsat-wrapper COAR_IMAGE=coar:latest fn apply(f: F) -> i32 where F: FnOnce(i32) -> i32, diff --git a/tests/ui/fail/closure_postcondition.rs b/tests/ui/fail/closure_postcondition.rs index 88263baf..9e00012a 100644 --- a/tests/ui/fail/closure_postcondition.rs +++ b/tests/ui/fail/closure_postcondition.rs @@ -1,6 +1,6 @@ //@error-in-other-file: Unsat //@compile-flags: -C debug-assertions=off - +//@rustc-env: THRUST_SOLVER=tests/thrust-pcsat-wrapper COAR_IMAGE=coar:latest #[thrust_macros::requires(thrust_macros::pre!(f(x)))] #[thrust_macros::ensures(thrust_macros::post!(f(x), result))] fn apply i32>(x: i32, f: F) -> i32 { diff --git a/tests/ui/fail/closure_receiver_mut_model.rs b/tests/ui/fail/closure_receiver_mut_model.rs index 337a20e9..33086d6d 100644 --- a/tests/ui/fail/closure_receiver_mut_model.rs +++ b/tests/ui/fail/closure_receiver_mut_model.rs @@ -1,6 +1,6 @@ //@error-in-other-file: Unsat //@compile-flags: -C debug-assertions=off -//@rustc-env: THRUST_SOLVER=tests/thrust-pcsat-wrapper +//@rustc-env: THRUST_SOLVER=tests/thrust-pcsat-wrapper COAR_IMAGE=coar:latest use thrust_models::{exists, model::{Mut, Int}}; diff --git a/tests/ui/fail/closure_receiver_mut_model_byval.rs b/tests/ui/fail/closure_receiver_mut_model_byval.rs index fd78c749..802f219c 100644 --- a/tests/ui/fail/closure_receiver_mut_model_byval.rs +++ b/tests/ui/fail/closure_receiver_mut_model_byval.rs @@ -1,6 +1,6 @@ //@error-in-other-file: Unsat //@compile-flags: -C debug-assertions=off -//@rustc-env: THRUST_SOLVER=tests/thrust-pcsat-wrapper +//@rustc-env: THRUST_SOLVER=tests/thrust-pcsat-wrapper COAR_IMAGE=coar:latest use thrust_models::{ exists, diff --git a/tests/ui/fail/closure_ref_mut_pre_post.rs b/tests/ui/fail/closure_ref_mut_pre_post.rs index e409ee6a..1c84f35f 100644 --- a/tests/ui/fail/closure_ref_mut_pre_post.rs +++ b/tests/ui/fail/closure_ref_mut_pre_post.rs @@ -1,6 +1,6 @@ //@error-in-other-file: Unsat //@compile-flags: -C debug-assertions=off - +//@rustc-env: THRUST_SOLVER=tests/thrust-pcsat-wrapper COAR_IMAGE=coar:latest #[thrust_macros::requires(thrust_macros::pre!(f()))] #[thrust_macros::ensures(thrust_macros::post!(f(), result))] fn call i64>(f: &mut F) -> i64 { diff --git a/tests/ui/fail/closure_ref_pre_post.rs b/tests/ui/fail/closure_ref_pre_post.rs index 1a96712f..5acf67eb 100644 --- a/tests/ui/fail/closure_ref_pre_post.rs +++ b/tests/ui/fail/closure_ref_pre_post.rs @@ -1,6 +1,6 @@ //@error-in-other-file: Unsat //@compile-flags: -C debug-assertions=off - +//@rustc-env: THRUST_SOLVER=tests/thrust-pcsat-wrapper COAR_IMAGE=coar:latest #[thrust_macros::requires(thrust_macros::pre!(f(x)))] #[thrust_macros::ensures(thrust_macros::post!(f(x), result))] fn apply_ref i32>(x: i32, f: &F) -> i32 { diff --git a/tests/ui/fail/closure_requires_ensures.rs b/tests/ui/fail/closure_requires_ensures.rs index e9f79ae2..5eaec9e5 100644 --- a/tests/ui/fail/closure_requires_ensures.rs +++ b/tests/ui/fail/closure_requires_ensures.rs @@ -1,6 +1,6 @@ //@error-in-other-file: Unsat //@compile-flags: -C debug-assertions=off - +//@rustc-env: THRUST_SOLVER=tests/thrust-pcsat-wrapper COAR_IMAGE=coar:latest // The declared postcondition `result > x` hides the body's exact result, so `r == 4` // is not provable. Were the postcondition inferred instead, it would be exact and the // assertion would hold. diff --git a/tests/ui/fail/closure_requires_only.rs b/tests/ui/fail/closure_requires_only.rs index 8aa8bcad..cc552d7d 100644 --- a/tests/ui/fail/closure_requires_only.rs +++ b/tests/ui/fail/closure_requires_only.rs @@ -1,6 +1,6 @@ //@error-in-other-file: Unsat //@compile-flags: -C debug-assertions=off - +//@rustc-env: THRUST_SOLVER=tests/thrust-pcsat-wrapper COAR_IMAGE=coar:latest // `-1` violates the declared precondition `x > 0`. Were the precondition inferred // instead, it would be weak enough to admit the call. #[thrust_macros::requires(thrust_macros::pre!(f(x)))] diff --git a/tests/ui/fail/fn_poly.rs b/tests/ui/fail/fn_poly.rs index 15351dde..2077376c 100644 --- a/tests/ui/fail/fn_poly.rs +++ b/tests/ui/fail/fn_poly.rs @@ -1,4 +1,5 @@ //@error-in-other-file: Unsat +//@rustc-env: THRUST_SOLVER=tests/thrust-pcsat-wrapper COAR_IMAGE=coar:latest fn left(x: (T, U)) -> T { x.0 diff --git a/tests/ui/fail/fn_poly_annot.rs b/tests/ui/fail/fn_poly_annot.rs index 471c7e56..1d796aaa 100644 --- a/tests/ui/fail/fn_poly_annot.rs +++ b/tests/ui/fail/fn_poly_annot.rs @@ -1,4 +1,5 @@ //@error-in-other-file: Unsat +//@rustc-env: THRUST_SOLVER=tests/thrust-pcsat-wrapper COAR_IMAGE=coar:latest #[thrust_macros::requires(true)] #[thrust_macros::ensures(result != x.0)] diff --git a/tests/ui/fail/fn_poly_annot_2.rs b/tests/ui/fail/fn_poly_annot_2.rs index 9ccd9513..806fc8b9 100644 --- a/tests/ui/fail/fn_poly_annot_2.rs +++ b/tests/ui/fail/fn_poly_annot_2.rs @@ -1,6 +1,6 @@ //@error-in-other-file: Unsat //@compile-flags: -C debug-assertions=off - +//@rustc-env: THRUST_SOLVER=tests/thrust-pcsat-wrapper COAR_IMAGE=coar:latest #[thrust_macros::requires(true)] #[thrust_macros::ensures(result == x)] fn id(x: i32, _t: T) -> i32 { diff --git a/tests/ui/fail/fn_poly_annot_complex.rs b/tests/ui/fail/fn_poly_annot_complex.rs index 3377d03c..7374e68e 100644 --- a/tests/ui/fail/fn_poly_annot_complex.rs +++ b/tests/ui/fail/fn_poly_annot_complex.rs @@ -1,4 +1,5 @@ //@error-in-other-file: Unsat +//@rustc-env: THRUST_SOLVER=tests/thrust-pcsat-wrapper COAR_IMAGE=coar:latest #[thrust_macros::requires((x.0 > 0) && (x.1 > 0))] #[thrust_macros::ensures((result.0 == x.1) && (result.1 == x.0))] diff --git a/tests/ui/fail/fn_poly_annot_multi_inst.rs b/tests/ui/fail/fn_poly_annot_multi_inst.rs index 35e2279e..76d51245 100644 --- a/tests/ui/fail/fn_poly_annot_multi_inst.rs +++ b/tests/ui/fail/fn_poly_annot_multi_inst.rs @@ -1,4 +1,5 @@ //@error-in-other-file: Unsat +//@rustc-env: THRUST_SOLVER=tests/thrust-pcsat-wrapper COAR_IMAGE=coar:latest #[thrust_macros::requires(true)] #[thrust_macros::ensures(result == x)] diff --git a/tests/ui/fail/fn_poly_annot_nested.rs b/tests/ui/fail/fn_poly_annot_nested.rs index 888dd162..d5d83c46 100644 --- a/tests/ui/fail/fn_poly_annot_nested.rs +++ b/tests/ui/fail/fn_poly_annot_nested.rs @@ -1,4 +1,5 @@ //@error-in-other-file: Unsat +//@rustc-env: THRUST_SOLVER=tests/thrust-pcsat-wrapper COAR_IMAGE=coar:latest #[thrust_macros::requires(true)] #[thrust_macros::ensures(result == x)] diff --git a/tests/ui/fail/fn_poly_annot_recursive.rs b/tests/ui/fail/fn_poly_annot_recursive.rs index a35fa950..39e7e997 100644 --- a/tests/ui/fail/fn_poly_annot_recursive.rs +++ b/tests/ui/fail/fn_poly_annot_recursive.rs @@ -1,6 +1,6 @@ //@error-in-other-file: Unsat //@compile-flags: -C debug-assertions=off - +//@rustc-env: THRUST_SOLVER=tests/thrust-pcsat-wrapper COAR_IMAGE=coar:latest #[thrust_macros::requires(n >= 0)] #[thrust_macros::ensures(result == value)] fn repeat(n: i32, value: T) -> T { diff --git a/tests/ui/fail/fn_poly_annot_ref.rs b/tests/ui/fail/fn_poly_annot_ref.rs index c1443b99..b81c86ed 100644 --- a/tests/ui/fail/fn_poly_annot_ref.rs +++ b/tests/ui/fail/fn_poly_annot_ref.rs @@ -1,4 +1,5 @@ //@error-in-other-file: Unsat +//@rustc-env: THRUST_SOLVER=tests/thrust-pcsat-wrapper COAR_IMAGE=coar:latest #[thrust_macros::requires(true)] #[thrust_macros::ensures(result != x)] diff --git a/tests/ui/fail/fn_poly_annot_singleton.rs b/tests/ui/fail/fn_poly_annot_singleton.rs index ecdc6183..ccd61964 100644 --- a/tests/ui/fail/fn_poly_annot_singleton.rs +++ b/tests/ui/fail/fn_poly_annot_singleton.rs @@ -1,6 +1,6 @@ //@error-in-other-file: Unsat //@compile-flags: -C debug-assertions=off - +//@rustc-env: THRUST_SOLVER=tests/thrust-pcsat-wrapper COAR_IMAGE=coar:latest #[thrust_macros::requires(true)] #[thrust_macros::ensures(x == x && result == 1)] fn unit_value(x: T) -> i64 { diff --git a/tests/ui/fail/fn_poly_annot_stronger.rs b/tests/ui/fail/fn_poly_annot_stronger.rs index 1e9bf33a..10059217 100644 --- a/tests/ui/fail/fn_poly_annot_stronger.rs +++ b/tests/ui/fail/fn_poly_annot_stronger.rs @@ -1,6 +1,6 @@ //@error-in-other-file: Unsat //@compile-flags: -C debug-assertions=off - +//@rustc-env: THRUST_SOLVER=tests/thrust-pcsat-wrapper COAR_IMAGE=coar:latest #[thrust_macros::requires(x > 0)] #[thrust_macros::ensures((result == x) && (result > 0))] fn pass_positive(x: i32, _dummy: T) -> i32 { diff --git a/tests/ui/fail/fn_poly_multiple_calls.rs b/tests/ui/fail/fn_poly_multiple_calls.rs index f510eabb..0db31c86 100644 --- a/tests/ui/fail/fn_poly_multiple_calls.rs +++ b/tests/ui/fail/fn_poly_multiple_calls.rs @@ -1,4 +1,5 @@ //@error-in-other-file: Unsat +//@rustc-env: THRUST_SOLVER=tests/thrust-pcsat-wrapper COAR_IMAGE=coar:latest fn first(pair: (T, U)) -> T { pair.0 diff --git a/tests/ui/fail/fn_poly_mut_ref.rs b/tests/ui/fail/fn_poly_mut_ref.rs index 1ab6ff99..3ebb1ee7 100644 --- a/tests/ui/fail/fn_poly_mut_ref.rs +++ b/tests/ui/fail/fn_poly_mut_ref.rs @@ -1,4 +1,5 @@ //@error-in-other-file: Unsat +//@rustc-env: THRUST_SOLVER=tests/thrust-pcsat-wrapper COAR_IMAGE=coar:latest fn update(x: &mut T, new_val: T) { *x = new_val; diff --git a/tests/ui/fail/fn_poly_param_order.rs b/tests/ui/fail/fn_poly_param_order.rs index 93af596d..224214ab 100644 --- a/tests/ui/fail/fn_poly_param_order.rs +++ b/tests/ui/fail/fn_poly_param_order.rs @@ -1,4 +1,5 @@ //@error-in-other-file: Unsat +//@rustc-env: THRUST_SOLVER=tests/thrust-pcsat-wrapper COAR_IMAGE=coar:latest fn select(a: T, b: U, c: V, which: i32) -> T { if which == 0 { diff --git a/tests/ui/fail/fn_poly_recursive.rs b/tests/ui/fail/fn_poly_recursive.rs index 8b2aaba3..3ec6fada 100644 --- a/tests/ui/fail/fn_poly_recursive.rs +++ b/tests/ui/fail/fn_poly_recursive.rs @@ -1,6 +1,6 @@ //@error-in-other-file: Unsat //@compile-flags: -C debug-assertions=off - +//@rustc-env: THRUST_SOLVER=tests/thrust-pcsat-wrapper COAR_IMAGE=coar:latest fn repeat(n: i32, value: T) -> T { if n <= 1 { value diff --git a/tests/ui/fail/fn_poly_ref.rs b/tests/ui/fail/fn_poly_ref.rs index 671f8b03..8e8ef725 100644 --- a/tests/ui/fail/fn_poly_ref.rs +++ b/tests/ui/fail/fn_poly_ref.rs @@ -1,4 +1,5 @@ //@error-in-other-file: Unsat +//@rustc-env: THRUST_SOLVER=tests/thrust-pcsat-wrapper COAR_IMAGE=coar:latest fn identity_ref(x: &T) -> &T { x diff --git a/tests/ui/fail/fn_poly_ref_ord.rs b/tests/ui/fail/fn_poly_ref_ord.rs index 13966f05..e2cea104 100644 --- a/tests/ui/fail/fn_poly_ref_ord.rs +++ b/tests/ui/fail/fn_poly_ref_ord.rs @@ -1,4 +1,5 @@ //@error-in-other-file: Unsat +//@rustc-env: THRUST_SOLVER=tests/thrust-pcsat-wrapper COAR_IMAGE=coar:latest fn lt(x: &T, y: &T) -> bool where T: Ord { x < y diff --git a/tests/ui/fail/fn_poly_unused_param.rs b/tests/ui/fail/fn_poly_unused_param.rs index bde86f1e..78d85af0 100644 --- a/tests/ui/fail/fn_poly_unused_param.rs +++ b/tests/ui/fail/fn_poly_unused_param.rs @@ -1,4 +1,5 @@ //@error-in-other-file: Unsat +//@rustc-env: THRUST_SOLVER=tests/thrust-pcsat-wrapper COAR_IMAGE=coar:latest fn project_first(triple: (T, U, V)) -> T { triple.0 diff --git a/tests/ui/fail/issue_108.rs b/tests/ui/fail/issue_108.rs index cc05c1a1..fa6547d3 100644 --- a/tests/ui/fail/issue_108.rs +++ b/tests/ui/fail/issue_108.rs @@ -1,6 +1,6 @@ //@error-in-other-file: Unsat //@compile-flags: -C debug-assertions=off - +//@rustc-env: THRUST_SOLVER=tests/thrust-pcsat-wrapper COAR_IMAGE=coar:latest fn apply T>(x: T, f: F) -> T { f(x) } diff --git a/tests/ui/fail/iterators/annot_range_loop.rs b/tests/ui/fail/iterators/annot_range_loop.rs index fa8f544b..b1f40894 100644 --- a/tests/ui/fail/iterators/annot_range_loop.rs +++ b/tests/ui/fail/iterators/annot_range_loop.rs @@ -1,6 +1,6 @@ //@error-in-other-file: Unsat //@compile-flags: -C debug-assertions=off -//@rustc-env: THRUST_SOLVER=tests/thrust-pcsat-wrapper THRUST_SOLVER_TIMEOUT_SECS=60 +//@rustc-env: THRUST_SOLVER=tests/thrust-pcsat-wrapper THRUST_SOLVER_TIMEOUT_SECS=60 COAR_IMAGE=coar:latest #[thrust_macros::context] trait Iterator { diff --git a/tests/ui/fail/iterators/annot_range_next.rs b/tests/ui/fail/iterators/annot_range_next.rs index a7cdfc07..becc9240 100644 --- a/tests/ui/fail/iterators/annot_range_next.rs +++ b/tests/ui/fail/iterators/annot_range_next.rs @@ -1,6 +1,6 @@ //@error-in-other-file: Unsat //@compile-flags: -C debug-assertions=off -//@rustc-env: THRUST_SOLVER=tests/thrust-pcsat-wrapper +//@rustc-env: THRUST_SOLVER=tests/thrust-pcsat-wrapper COAR_IMAGE=coar:latest #[thrust_macros::context] trait Iterator { diff --git a/tests/ui/fail/iterators/fixed_filter_loop_none.rs b/tests/ui/fail/iterators/fixed_filter_loop_none.rs index 2c213c5f..4b743c5d 100644 --- a/tests/ui/fail/iterators/fixed_filter_loop_none.rs +++ b/tests/ui/fail/iterators/fixed_filter_loop_none.rs @@ -1,5 +1,6 @@ //@error-in-other-file: Unsat //@compile-flags: -C debug-assertions=off +//@rustc-env: THRUST_SOLVER=tests/thrust-pcsat-wrapper COAR_IMAGE=coar:latest struct Range { start: i64, diff --git a/tests/ui/fail/iterators/fixed_filter_next_some.rs b/tests/ui/fail/iterators/fixed_filter_next_some.rs index 7f3f16cc..fb08b0bf 100644 --- a/tests/ui/fail/iterators/fixed_filter_next_some.rs +++ b/tests/ui/fail/iterators/fixed_filter_next_some.rs @@ -1,5 +1,6 @@ //@error-in-other-file: Unsat //@compile-flags: -C debug-assertions=off +//@rustc-env: THRUST_SOLVER=tests/thrust-pcsat-wrapper COAR_IMAGE=coar:latest struct Range { start: i64, diff --git a/tests/ui/fail/iterators/range.rs b/tests/ui/fail/iterators/range.rs index 1af93a70..d303a4b8 100644 --- a/tests/ui/fail/iterators/range.rs +++ b/tests/ui/fail/iterators/range.rs @@ -1,6 +1,6 @@ //@error-in-other-file: Unsat //@compile-flags: -C debug-assertions=off -//@rustc-env: THRUST_SOLVER_ARGS= +//@rustc-env: THRUST_SOLVER=tests/thrust-pcsat-wrapper THRUST_SOLVER_ARGS= COAR_IMAGE=coar:latest struct Range { start: i64, diff --git a/tests/ui/fail/loop_invariant_fn_param_closure.rs b/tests/ui/fail/loop_invariant_fn_param_closure.rs index c808b111..dde7e62b 100644 --- a/tests/ui/fail/loop_invariant_fn_param_closure.rs +++ b/tests/ui/fail/loop_invariant_fn_param_closure.rs @@ -1,6 +1,6 @@ //@error-in-other-file: Unsat //@compile-flags: -C debug-assertions=off - +//@rustc-env: THRUST_SOLVER=tests/thrust-pcsat-wrapper COAR_IMAGE=coar:latest // A loop invariant refers to a closure parameter via `FnParam`, whose // `f.at_entry()` yields `Closure`. Here the invariant relates `acc` to the // entry closure's postcondition, from which the postcondition below is proven. diff --git a/tests/ui/fail/loop_invariant_generic.rs b/tests/ui/fail/loop_invariant_generic.rs index 5342e970..409dbd12 100644 --- a/tests/ui/fail/loop_invariant_generic.rs +++ b/tests/ui/fail/loop_invariant_generic.rs @@ -1,6 +1,6 @@ //@error-in-other-file: Unsat //@compile-flags: -C debug-assertions=off - +//@rustc-env: THRUST_SOLVER=tests/thrust-pcsat-wrapper COAR_IMAGE=coar:latest #[thrust_macros::requires(true)] #[thrust_macros::ensures(true)] #[thrust::trusted] diff --git a/tests/ui/fail/loop_invariant_trait.rs b/tests/ui/fail/loop_invariant_trait.rs index cc7d95ed..2e12157d 100644 --- a/tests/ui/fail/loop_invariant_trait.rs +++ b/tests/ui/fail/loop_invariant_trait.rs @@ -1,6 +1,6 @@ //@error-in-other-file: Unsat //@compile-flags: -C debug-assertions=off - +//@rustc-env: THRUST_SOLVER=tests/thrust-pcsat-wrapper COAR_IMAGE=coar:latest #[thrust_macros::requires(true)] #[thrust_macros::ensures(true)] #[thrust::trusted] diff --git a/tests/ui/fail/option_inc.rs b/tests/ui/fail/option_inc.rs index 4e98b06b..fe6d3238 100644 --- a/tests/ui/fail/option_inc.rs +++ b/tests/ui/fail/option_inc.rs @@ -1,5 +1,6 @@ //@error-in-other-file: Unsat //@compile-flags: -C debug-assertions=off +//@rustc-env: THRUST_SOLVER=tests/thrust-pcsat-wrapper COAR_IMAGE=coar:latest fn maybe_inc(x: i32, do_it: bool) -> Option { if do_it { diff --git a/tests/ui/fail/option_loop.rs b/tests/ui/fail/option_loop.rs index a010af40..06970d98 100644 --- a/tests/ui/fail/option_loop.rs +++ b/tests/ui/fail/option_loop.rs @@ -1,5 +1,6 @@ //@error-in-other-file: Unsat //@compile-flags: -C debug-assertions=off +//@rustc-env: THRUST_SOLVER=tests/thrust-pcsat-wrapper COAR_IMAGE=coar:latest fn main() { let mut opt = Some(5); diff --git a/tests/ui/fail/option_map.rs b/tests/ui/fail/option_map.rs index df5889ee..6c70f970 100644 --- a/tests/ui/fail/option_map.rs +++ b/tests/ui/fail/option_map.rs @@ -1,6 +1,6 @@ //@error-in-other-file: Unsat //@compile-flags: -C debug-assertions=off -//@rustc-env: THRUST_SOLVER=tests/thrust-pcsat-wrapper +//@rustc-env: THRUST_SOLVER=tests/thrust-pcsat-wrapper COAR_IMAGE=coar:latest #[thrust::callable] fn check(opt: Option) { diff --git a/tests/ui/fail/option_mut.rs b/tests/ui/fail/option_mut.rs index 827bc9c1..c11291cc 100644 --- a/tests/ui/fail/option_mut.rs +++ b/tests/ui/fail/option_mut.rs @@ -1,5 +1,6 @@ //@error-in-other-file: Unsat //@compile-flags: -C debug-assertions=off +//@rustc-env: THRUST_SOLVER=tests/thrust-pcsat-wrapper COAR_IMAGE=coar:latest fn main() { let mut m: Option = Some(1); diff --git a/tests/ui/fail/option_unwrap_or_else.rs b/tests/ui/fail/option_unwrap_or_else.rs index 397f31a5..88357f1d 100644 --- a/tests/ui/fail/option_unwrap_or_else.rs +++ b/tests/ui/fail/option_unwrap_or_else.rs @@ -1,6 +1,6 @@ //@error-in-other-file: Unsat //@compile-flags: -C debug-assertions=off -//@rustc-env: THRUST_SOLVER=tests/thrust-pcsat-wrapper +//@rustc-env: THRUST_SOLVER=tests/thrust-pcsat-wrapper COAR_IMAGE=coar:latest #[thrust::callable] fn check(o: Option, d: i32) { diff --git a/tests/ui/fail/refine_param_generic_adt.rs b/tests/ui/fail/refine_param_generic_adt.rs index 9c7673e1..90b3c32f 100644 --- a/tests/ui/fail/refine_param_generic_adt.rs +++ b/tests/ui/fail/refine_param_generic_adt.rs @@ -1,3 +1,4 @@ +//@rustc-env: THRUST_SOLVER=tests/thrust-pcsat-wrapper COAR_IMAGE=coar:latest //@error-in-other-file: Unsat pub enum Pair { diff --git a/tests/ui/fail/refine_param_nested_binder.rs b/tests/ui/fail/refine_param_nested_binder.rs index c75acfe9..ed260f1e 100644 --- a/tests/ui/fail/refine_param_nested_binder.rs +++ b/tests/ui/fail/refine_param_nested_binder.rs @@ -1,3 +1,4 @@ +//@rustc-env: THRUST_SOLVER=tests/thrust-pcsat-wrapper COAR_IMAGE=coar:latest //@error-in-other-file: Unsat pub enum Pair { diff --git a/tests/ui/fail/refine_param_path_qualified.rs b/tests/ui/fail/refine_param_path_qualified.rs index 3bbf8170..4734f0ea 100644 --- a/tests/ui/fail/refine_param_path_qualified.rs +++ b/tests/ui/fail/refine_param_path_qualified.rs @@ -1,3 +1,4 @@ +//@rustc-env: THRUST_SOLVER=tests/thrust-pcsat-wrapper COAR_IMAGE=coar:latest //@error-in-other-file: Unsat pub enum Pair { diff --git a/tests/ui/fail/refine_sig_generic_adt.rs b/tests/ui/fail/refine_sig_generic_adt.rs index f038f744..666cba94 100644 --- a/tests/ui/fail/refine_sig_generic_adt.rs +++ b/tests/ui/fail/refine_sig_generic_adt.rs @@ -1,3 +1,4 @@ +//@rustc-env: THRUST_SOLVER=tests/thrust-pcsat-wrapper COAR_IMAGE=coar:latest //@error-in-other-file: Unsat pub enum Pair { diff --git a/tests/ui/fail/result_mut.rs b/tests/ui/fail/result_mut.rs index 7baa46bd..ca9033c8 100644 --- a/tests/ui/fail/result_mut.rs +++ b/tests/ui/fail/result_mut.rs @@ -1,5 +1,6 @@ //@error-in-other-file: Unsat //@compile-flags: -C debug-assertions=off +//@rustc-env: THRUST_SOLVER=tests/thrust-pcsat-wrapper COAR_IMAGE=coar:latest fn mutate_res(r: &mut Result) { match r { diff --git a/tests/ui/fail/result_struct.rs b/tests/ui/fail/result_struct.rs index cb164209..a0021328 100644 --- a/tests/ui/fail/result_struct.rs +++ b/tests/ui/fail/result_struct.rs @@ -1,5 +1,6 @@ //@error-in-other-file: Unsat //@compile-flags: -C debug-assertions=off +//@rustc-env: THRUST_SOLVER=tests/thrust-pcsat-wrapper COAR_IMAGE=coar:latest struct Point { x: i32, diff --git a/tests/ui/fail/slice_first_mut.rs b/tests/ui/fail/slice_first_mut.rs index 7800f2c8..59a0e20b 100644 --- a/tests/ui/fail/slice_first_mut.rs +++ b/tests/ui/fail/slice_first_mut.rs @@ -1,5 +1,6 @@ //@error-in-other-file: Unsat //@compile-flags: -C debug-assertions=off +//@rustc-env: THRUST_SOLVER=tests/thrust-pcsat-wrapper COAR_IMAGE=coar:latest #[thrust::trusted] #[thrust_macros::requires(true)] diff --git a/tests/ui/fail/slice_last_mut.rs b/tests/ui/fail/slice_last_mut.rs index 10bcedfb..bc20a9a7 100644 --- a/tests/ui/fail/slice_last_mut.rs +++ b/tests/ui/fail/slice_last_mut.rs @@ -1,5 +1,6 @@ //@error-in-other-file: Unsat //@compile-flags: -C debug-assertions=off +//@rustc-env: THRUST_SOLVER=tests/thrust-pcsat-wrapper COAR_IMAGE=coar:latest #[thrust::trusted] #[thrust_macros::requires(true)] diff --git a/tests/ui/fail/slice_methods.rs b/tests/ui/fail/slice_methods.rs index fb57d55e..8bd63d85 100644 --- a/tests/ui/fail/slice_methods.rs +++ b/tests/ui/fail/slice_methods.rs @@ -1,5 +1,6 @@ //@error-in-other-file: Unsat //@compile-flags: -C debug-assertions=off +//@rustc-env: THRUST_SOLVER=tests/thrust-pcsat-wrapper COAR_IMAGE=coar:latest #[thrust::trusted] #[thrust_macros::requires(true)] diff --git a/tests/ui/fail/slice_methods_mut.rs b/tests/ui/fail/slice_methods_mut.rs index a01f3ecb..1d1a466d 100644 --- a/tests/ui/fail/slice_methods_mut.rs +++ b/tests/ui/fail/slice_methods_mut.rs @@ -1,5 +1,6 @@ //@error-in-other-file: Unsat //@compile-flags: -C debug-assertions=off +//@rustc-env: THRUST_SOLVER=tests/thrust-pcsat-wrapper COAR_IMAGE=coar:latest #[thrust::trusted] #[thrust_macros::requires(true)] diff --git a/tests/ui/fail/trait_assoc_type_spec.rs b/tests/ui/fail/trait_assoc_type_spec.rs index b040ef09..cb58fd5d 100644 --- a/tests/ui/fail/trait_assoc_type_spec.rs +++ b/tests/ui/fail/trait_assoc_type_spec.rs @@ -1,6 +1,6 @@ //@error-in-other-file: Unsat //@compile-flags: -Adead_code -C debug-assertions=off -//@rustc-env: THRUST_SOLVER=tests/thrust-pcsat-wrapper +//@rustc-env: THRUST_SOLVER=tests/thrust-pcsat-wrapper COAR_IMAGE=coar:latest #[thrust_macros::context] trait Source { diff --git a/tests/ui/fail/vec_2.rs b/tests/ui/fail/vec_2.rs index 420adb77..57a3e95d 100644 --- a/tests/ui/fail/vec_2.rs +++ b/tests/ui/fail/vec_2.rs @@ -1,5 +1,6 @@ //@error-in-other-file: Unsat //@compile-flags: -C debug-assertions=off +//@rustc-env: THRUST_SOLVER=tests/thrust-pcsat-wrapper COAR_IMAGE=coar:latest fn main() { let mut v = Vec::new(); diff --git a/tests/ui/pass/adt_enum_field.rs b/tests/ui/pass/adt_enum_field.rs index 0109572f..70384386 100644 --- a/tests/ui/pass/adt_enum_field.rs +++ b/tests/ui/pass/adt_enum_field.rs @@ -1,5 +1,6 @@ //@check-pass //@compile-flags: -Adead_code -C debug-assertions=off +//@rustc-env: THRUST_SOLVER=tests/thrust-pcsat-wrapper COAR_IMAGE=coar:latest struct Wrap { o: Option, diff --git a/tests/ui/pass/adt_poly_fn_mono.rs b/tests/ui/pass/adt_poly_fn_mono.rs index eb1d1418..95cd0dea 100644 --- a/tests/ui/pass/adt_poly_fn_mono.rs +++ b/tests/ui/pass/adt_poly_fn_mono.rs @@ -1,5 +1,6 @@ //@check-pass //@compile-flags: -C debug-assertions=off +//@rustc-env: THRUST_SOLVER=tests/thrust-pcsat-wrapper COAR_IMAGE=coar:latest pub enum X { A(T), diff --git a/tests/ui/pass/adt_poly_ref.rs b/tests/ui/pass/adt_poly_ref.rs index f0e5e301..f6964a93 100644 --- a/tests/ui/pass/adt_poly_ref.rs +++ b/tests/ui/pass/adt_poly_ref.rs @@ -1,5 +1,6 @@ //@check-pass //@compile-flags: -C debug-assertions=off +//@rustc-env: THRUST_SOLVER=tests/thrust-pcsat-wrapper COAR_IMAGE=coar:latest enum X<'a, T> { A(&'a T), diff --git a/tests/ui/pass/adt_variant_without_params.rs b/tests/ui/pass/adt_variant_without_params.rs index 15bd1e42..0d201697 100644 --- a/tests/ui/pass/adt_variant_without_params.rs +++ b/tests/ui/pass/adt_variant_without_params.rs @@ -1,3 +1,4 @@ +//@rustc-env: THRUST_SOLVER=tests/thrust-pcsat-wrapper COAR_IMAGE=coar:latest //@check-pass enum X { diff --git a/tests/ui/pass/annot_mut_term_formula_fn_poly.rs b/tests/ui/pass/annot_mut_term_formula_fn_poly.rs index ffdda7e4..d361120f 100644 --- a/tests/ui/pass/annot_mut_term_formula_fn_poly.rs +++ b/tests/ui/pass/annot_mut_term_formula_fn_poly.rs @@ -1,4 +1,5 @@ //@check-pass +//@rustc-env: THRUST_SOLVER=tests/thrust-pcsat-wrapper COAR_IMAGE=coar:latest #[allow(unused_variables)] #[thrust::formula_fn] diff --git a/tests/ui/pass/annot_preds.rs b/tests/ui/pass/annot_preds.rs index 516cb6ff..7433d894 100644 --- a/tests/ui/pass/annot_preds.rs +++ b/tests/ui/pass/annot_preds.rs @@ -1,5 +1,6 @@ //@check-pass //@compile-flags: -Adead_code -C debug-assertions=off +//@rustc-env: THRUST_SOLVER=tests/thrust-pcsat-wrapper COAR_IMAGE=coar:latest #[thrust::predicate] fn is_double(x: thrust_models::model::Int, doubled_x: thrust_models::model::Int) -> bool { diff --git a/tests/ui/pass/closure_captures.rs b/tests/ui/pass/closure_captures.rs index 560f80da..3c24db56 100644 --- a/tests/ui/pass/closure_captures.rs +++ b/tests/ui/pass/closure_captures.rs @@ -1,6 +1,6 @@ //@check-pass //@compile-flags: -C debug-assertions=off - +//@rustc-env: THRUST_SOLVER=tests/thrust-pcsat-wrapper COAR_IMAGE=coar:latest #[thrust_macros::requires(thrust_macros::pre!(f(x)))] #[thrust_macros::ensures(thrust_macros::post!(f(x), result))] fn apply i32>(x: i32, f: F) -> i32 { diff --git a/tests/ui/pass/closure_captures_fn_once.rs b/tests/ui/pass/closure_captures_fn_once.rs index bac7d0ec..e44a6cf1 100644 --- a/tests/ui/pass/closure_captures_fn_once.rs +++ b/tests/ui/pass/closure_captures_fn_once.rs @@ -1,6 +1,6 @@ //@check-pass //@compile-flags: -C debug-assertions=off - +//@rustc-env: THRUST_SOLVER=tests/thrust-pcsat-wrapper COAR_IMAGE=coar:latest // Passed straight to `apply` to keep the closure `FnOnce`: binding it to a `let` first // makes it `FnMut`, which holds its upvars behind another `Mut`. #[thrust_macros::requires(thrust_macros::pre!(f(x)))] diff --git a/tests/ui/pass/closure_ensures_only.rs b/tests/ui/pass/closure_ensures_only.rs index 83e5e8b5..122e69c7 100644 --- a/tests/ui/pass/closure_ensures_only.rs +++ b/tests/ui/pass/closure_ensures_only.rs @@ -1,6 +1,6 @@ //@check-pass //@compile-flags: -C debug-assertions=off - +//@rustc-env: THRUST_SOLVER=tests/thrust-pcsat-wrapper COAR_IMAGE=coar:latest // A closure that declares only `ensures`; its precondition stays inferred as a // predicate variable, so the caller has nothing to establish. #[thrust_macros::requires(thrust_macros::pre!(f(x)))] diff --git a/tests/ui/pass/closure_model_preserve.rs b/tests/ui/pass/closure_model_preserve.rs index 3bf6cc6e..6f6912cb 100644 --- a/tests/ui/pass/closure_model_preserve.rs +++ b/tests/ui/pass/closure_model_preserve.rs @@ -1,6 +1,6 @@ //@check-pass //@compile-flags: -C debug-assertions=off - +//@rustc-env: THRUST_SOLVER=tests/thrust-pcsat-wrapper COAR_IMAGE=coar:latest #[thrust_macros::ensures(result == f)] fn call i32>(mut f: F) -> F { f(); diff --git a/tests/ui/pass/closure_mut_capture_pre_post.rs b/tests/ui/pass/closure_mut_capture_pre_post.rs index 890c02ba..23e8e53f 100644 --- a/tests/ui/pass/closure_mut_capture_pre_post.rs +++ b/tests/ui/pass/closure_mut_capture_pre_post.rs @@ -1,6 +1,6 @@ //@check-pass //@compile-flags: -C debug-assertions=off - +//@rustc-env: THRUST_SOLVER=tests/thrust-pcsat-wrapper COAR_IMAGE=coar:latest // A closure that mutates a capture receives its upvars behind a `Mut`, while the // higher-order function names the closure by value in `pre!`/`post!`. #[thrust_macros::requires(thrust_macros::pre!(f()))] diff --git a/tests/ui/pass/closure_param.rs b/tests/ui/pass/closure_param.rs index 0188ca92..2d627aee 100644 --- a/tests/ui/pass/closure_param.rs +++ b/tests/ui/pass/closure_param.rs @@ -1,6 +1,6 @@ //@check-pass //@compile-flags: -C debug-assertions=off - +//@rustc-env: THRUST_SOLVER=tests/thrust-pcsat-wrapper COAR_IMAGE=coar:latest fn take_fn T>(f: F) -> T { f(41) } diff --git a/tests/ui/pass/closure_param_weaken_1.rs b/tests/ui/pass/closure_param_weaken_1.rs index 9eae6647..5e0480f7 100644 --- a/tests/ui/pass/closure_param_weaken_1.rs +++ b/tests/ui/pass/closure_param_weaken_1.rs @@ -1,6 +1,6 @@ //@check-pass //@compile-flags: -C debug-assertions=off - +//@rustc-env: THRUST_SOLVER=tests/thrust-pcsat-wrapper COAR_IMAGE=coar:latest fn apply(f: F) -> i32 where F: FnOnce(i32) -> i32, diff --git a/tests/ui/pass/closure_param_weaken_2.rs b/tests/ui/pass/closure_param_weaken_2.rs index 6f233a16..abcfacbe 100644 --- a/tests/ui/pass/closure_param_weaken_2.rs +++ b/tests/ui/pass/closure_param_weaken_2.rs @@ -1,6 +1,6 @@ //@check-pass //@compile-flags: -C debug-assertions=off - +//@rustc-env: THRUST_SOLVER=tests/thrust-pcsat-wrapper COAR_IMAGE=coar:latest fn apply(mut f: F) -> i32 where F: FnMut(i32) -> i32, diff --git a/tests/ui/pass/closure_param_weaken_3.rs b/tests/ui/pass/closure_param_weaken_3.rs index 14781d0a..7fc23efa 100644 --- a/tests/ui/pass/closure_param_weaken_3.rs +++ b/tests/ui/pass/closure_param_weaken_3.rs @@ -1,6 +1,6 @@ //@check-pass //@compile-flags: -C debug-assertions=off - +//@rustc-env: THRUST_SOLVER=tests/thrust-pcsat-wrapper COAR_IMAGE=coar:latest fn apply(f: F) -> i32 where F: FnOnce(i32) -> i32, diff --git a/tests/ui/pass/closure_postcondition.rs b/tests/ui/pass/closure_postcondition.rs index b56ff2b8..fa8be273 100644 --- a/tests/ui/pass/closure_postcondition.rs +++ b/tests/ui/pass/closure_postcondition.rs @@ -1,6 +1,6 @@ //@check-pass //@compile-flags: -C debug-assertions=off - +//@rustc-env: THRUST_SOLVER=tests/thrust-pcsat-wrapper COAR_IMAGE=coar:latest // A higher-order function whose specification refers to the pre-/post-conditions // of its closure argument via `pre!(f(..))` / `post!(f(..), result)`. #[thrust_macros::requires(thrust_macros::pre!(f(x)))] diff --git a/tests/ui/pass/closure_postcondition_generic.rs b/tests/ui/pass/closure_postcondition_generic.rs index bfca9815..e149da25 100644 --- a/tests/ui/pass/closure_postcondition_generic.rs +++ b/tests/ui/pass/closure_postcondition_generic.rs @@ -1,6 +1,6 @@ //@check-pass //@compile-flags: -C debug-assertions=off - +//@rustc-env: THRUST_SOLVER=tests/thrust-pcsat-wrapper COAR_IMAGE=coar:latest #[thrust_macros::requires(thrust_macros::pre!(f(x)))] #[thrust_macros::ensures(thrust_macros::post!(f(x), result))] fn apply T>(x: T, f: F) -> T { diff --git a/tests/ui/pass/closure_receiver_mut_model.rs b/tests/ui/pass/closure_receiver_mut_model.rs index f26c4318..4fb5b0df 100644 --- a/tests/ui/pass/closure_receiver_mut_model.rs +++ b/tests/ui/pass/closure_receiver_mut_model.rs @@ -1,6 +1,6 @@ //@check-pass //@compile-flags: -C debug-assertions=off -//@rustc-env: THRUST_SOLVER=tests/thrust-pcsat-wrapper +//@rustc-env: THRUST_SOLVER=tests/thrust-pcsat-wrapper COAR_IMAGE=coar:latest use thrust_models::{exists, model::{Mut, Int}}; diff --git a/tests/ui/pass/closure_receiver_mut_model_byval.rs b/tests/ui/pass/closure_receiver_mut_model_byval.rs index 1c242719..23c8b243 100644 --- a/tests/ui/pass/closure_receiver_mut_model_byval.rs +++ b/tests/ui/pass/closure_receiver_mut_model_byval.rs @@ -1,6 +1,6 @@ //@check-pass //@compile-flags: -C debug-assertions=off -//@rustc-env: THRUST_SOLVER=tests/thrust-pcsat-wrapper +//@rustc-env: THRUST_SOLVER=tests/thrust-pcsat-wrapper COAR_IMAGE=coar:latest use thrust_models::{ exists, diff --git a/tests/ui/pass/closure_ref_mut_pre_post.rs b/tests/ui/pass/closure_ref_mut_pre_post.rs index c61a17cc..c36bf450 100644 --- a/tests/ui/pass/closure_ref_mut_pre_post.rs +++ b/tests/ui/pass/closure_ref_mut_pre_post.rs @@ -1,6 +1,6 @@ //@check-pass //@compile-flags: -C debug-assertions=off - +//@rustc-env: THRUST_SOLVER=tests/thrust-pcsat-wrapper COAR_IMAGE=coar:latest // The higher-order function names the closure through a `&mut` in `pre!`/`post!`, while a // closure that only reads its captures receives its upvars as they are. #[thrust_macros::requires(thrust_macros::pre!(f()))] diff --git a/tests/ui/pass/closure_ref_pre_post.rs b/tests/ui/pass/closure_ref_pre_post.rs index cba8122d..38aef0ab 100644 --- a/tests/ui/pass/closure_ref_pre_post.rs +++ b/tests/ui/pass/closure_ref_pre_post.rs @@ -1,6 +1,6 @@ //@check-pass //@compile-flags: -C debug-assertions=off - +//@rustc-env: THRUST_SOLVER=tests/thrust-pcsat-wrapper COAR_IMAGE=coar:latest #[thrust_macros::requires(thrust_macros::pre!(f(x)))] #[thrust_macros::ensures(thrust_macros::post!(f(x), result))] fn apply_ref i32>(x: i32, f: &F) -> i32 { diff --git a/tests/ui/pass/closure_requires_ensures.rs b/tests/ui/pass/closure_requires_ensures.rs index a0d9118a..1b93c13d 100644 --- a/tests/ui/pass/closure_requires_ensures.rs +++ b/tests/ui/pass/closure_requires_ensures.rs @@ -1,6 +1,6 @@ //@check-pass //@compile-flags: -C debug-assertions=off - +//@rustc-env: THRUST_SOLVER=tests/thrust-pcsat-wrapper COAR_IMAGE=coar:latest // The declared postcondition `result > x` is weaker than what the body computes, and // the caller sees only the declared one. #[thrust_macros::requires(thrust_macros::pre!(f(x)))] diff --git a/tests/ui/pass/closure_requires_only.rs b/tests/ui/pass/closure_requires_only.rs index eeea3067..afea7d52 100644 --- a/tests/ui/pass/closure_requires_only.rs +++ b/tests/ui/pass/closure_requires_only.rs @@ -1,6 +1,6 @@ //@check-pass //@compile-flags: -C debug-assertions=off - +//@rustc-env: THRUST_SOLVER=tests/thrust-pcsat-wrapper COAR_IMAGE=coar:latest // A closure that declares only `requires`; its postcondition stays inferred as a // predicate variable, so the caller still learns the body's exact result. #[thrust_macros::requires(thrust_macros::pre!(f(x)))] diff --git a/tests/ui/pass/fn_poly.rs b/tests/ui/pass/fn_poly.rs index 4a8e678a..14990eb3 100644 --- a/tests/ui/pass/fn_poly.rs +++ b/tests/ui/pass/fn_poly.rs @@ -1,4 +1,5 @@ //@check-pass +//@rustc-env: THRUST_SOLVER=tests/thrust-pcsat-wrapper COAR_IMAGE=coar:latest fn left(x: (T, U)) -> T { x.0 diff --git a/tests/ui/pass/fn_poly_annot.rs b/tests/ui/pass/fn_poly_annot.rs index 79086ad1..0c43b209 100644 --- a/tests/ui/pass/fn_poly_annot.rs +++ b/tests/ui/pass/fn_poly_annot.rs @@ -1,4 +1,5 @@ //@check-pass +//@rustc-env: THRUST_SOLVER=tests/thrust-pcsat-wrapper COAR_IMAGE=coar:latest #[thrust_macros::requires(true)] #[thrust_macros::ensures(result == x.0)] diff --git a/tests/ui/pass/fn_poly_annot_2.rs b/tests/ui/pass/fn_poly_annot_2.rs index 7fd0dadb..4ace7a0e 100644 --- a/tests/ui/pass/fn_poly_annot_2.rs +++ b/tests/ui/pass/fn_poly_annot_2.rs @@ -1,6 +1,6 @@ //@check-pass //@compile-flags: -C debug-assertions=off - +//@rustc-env: THRUST_SOLVER=tests/thrust-pcsat-wrapper COAR_IMAGE=coar:latest #[thrust_macros::requires(true)] #[thrust_macros::ensures(result == x)] fn id(x: i32, _t: T) -> i32 { diff --git a/tests/ui/pass/fn_poly_annot_complex.rs b/tests/ui/pass/fn_poly_annot_complex.rs index b0f63a08..4784fb1d 100644 --- a/tests/ui/pass/fn_poly_annot_complex.rs +++ b/tests/ui/pass/fn_poly_annot_complex.rs @@ -1,6 +1,6 @@ //@check-pass //@compile-flags: -C debug-assertions=off - +//@rustc-env: THRUST_SOLVER=tests/thrust-pcsat-wrapper COAR_IMAGE=coar:latest #[thrust_macros::requires((n > 0) && (m > 0))] #[thrust_macros::ensures((result.0 == m) && (result.1 == n))] fn swap_pair(n: i32, m: i32, _phantom: T) -> (i32, i32) { diff --git a/tests/ui/pass/fn_poly_annot_multi_inst.rs b/tests/ui/pass/fn_poly_annot_multi_inst.rs index 33fbe21a..775f19e8 100644 --- a/tests/ui/pass/fn_poly_annot_multi_inst.rs +++ b/tests/ui/pass/fn_poly_annot_multi_inst.rs @@ -1,4 +1,5 @@ //@check-pass +//@rustc-env: THRUST_SOLVER=tests/thrust-pcsat-wrapper COAR_IMAGE=coar:latest #[thrust_macros::requires(true)] #[thrust_macros::ensures(result == x)] diff --git a/tests/ui/pass/fn_poly_annot_nested.rs b/tests/ui/pass/fn_poly_annot_nested.rs index 24c69f9d..72be1ca7 100644 --- a/tests/ui/pass/fn_poly_annot_nested.rs +++ b/tests/ui/pass/fn_poly_annot_nested.rs @@ -1,4 +1,5 @@ //@check-pass +//@rustc-env: THRUST_SOLVER=tests/thrust-pcsat-wrapper COAR_IMAGE=coar:latest #[thrust_macros::requires(true)] #[thrust_macros::ensures(result == x)] diff --git a/tests/ui/pass/fn_poly_annot_recursive.rs b/tests/ui/pass/fn_poly_annot_recursive.rs index a107e49e..8641ca6e 100644 --- a/tests/ui/pass/fn_poly_annot_recursive.rs +++ b/tests/ui/pass/fn_poly_annot_recursive.rs @@ -1,6 +1,6 @@ //@check-pass //@compile-flags: -C debug-assertions=off - +//@rustc-env: THRUST_SOLVER=tests/thrust-pcsat-wrapper COAR_IMAGE=coar:latest #[thrust_macros::requires(n >= 0)] #[thrust_macros::ensures(result == value)] fn repeat(n: i32, value: T) -> T { diff --git a/tests/ui/pass/fn_poly_annot_ref.rs b/tests/ui/pass/fn_poly_annot_ref.rs index a164c892..1a6fa682 100644 --- a/tests/ui/pass/fn_poly_annot_ref.rs +++ b/tests/ui/pass/fn_poly_annot_ref.rs @@ -1,4 +1,5 @@ //@check-pass +//@rustc-env: THRUST_SOLVER=tests/thrust-pcsat-wrapper COAR_IMAGE=coar:latest #[thrust_macros::requires(true)] #[thrust_macros::ensures(result == x)] diff --git a/tests/ui/pass/fn_poly_annot_singleton.rs b/tests/ui/pass/fn_poly_annot_singleton.rs index e61f4e9b..964ac6b9 100644 --- a/tests/ui/pass/fn_poly_annot_singleton.rs +++ b/tests/ui/pass/fn_poly_annot_singleton.rs @@ -1,6 +1,6 @@ //@check-pass //@compile-flags: -C debug-assertions=off - +//@rustc-env: THRUST_SOLVER=tests/thrust-pcsat-wrapper COAR_IMAGE=coar:latest #[thrust_macros::requires(true)] #[thrust_macros::ensures(x == x && result == 0)] fn unit_value(x: T) -> i64 { diff --git a/tests/ui/pass/fn_poly_annot_stronger.rs b/tests/ui/pass/fn_poly_annot_stronger.rs index 883e518a..0fccc2ef 100644 --- a/tests/ui/pass/fn_poly_annot_stronger.rs +++ b/tests/ui/pass/fn_poly_annot_stronger.rs @@ -1,6 +1,6 @@ //@check-pass //@compile-flags: -C debug-assertions=off - +//@rustc-env: THRUST_SOLVER=tests/thrust-pcsat-wrapper COAR_IMAGE=coar:latest #[thrust_macros::requires(x > 0)] #[thrust_macros::ensures((result == x) && (result > 0))] fn pass_positive(x: i32, _dummy: T) -> i32 { diff --git a/tests/ui/pass/fn_poly_multiple_calls.rs b/tests/ui/pass/fn_poly_multiple_calls.rs index aa83ba17..69ebf2c1 100644 --- a/tests/ui/pass/fn_poly_multiple_calls.rs +++ b/tests/ui/pass/fn_poly_multiple_calls.rs @@ -1,4 +1,5 @@ //@check-pass +//@rustc-env: THRUST_SOLVER=tests/thrust-pcsat-wrapper COAR_IMAGE=coar:latest fn first(pair: (T, U)) -> T { pair.0 diff --git a/tests/ui/pass/fn_poly_mut_ref.rs b/tests/ui/pass/fn_poly_mut_ref.rs index 4298a64c..336cb952 100644 --- a/tests/ui/pass/fn_poly_mut_ref.rs +++ b/tests/ui/pass/fn_poly_mut_ref.rs @@ -1,4 +1,5 @@ //@check-pass +//@rustc-env: THRUST_SOLVER=tests/thrust-pcsat-wrapper COAR_IMAGE=coar:latest fn update(x: &mut T, new_val: T) { *x = new_val; diff --git a/tests/ui/pass/fn_poly_param_order.rs b/tests/ui/pass/fn_poly_param_order.rs index 41191de3..d3ee8313 100644 --- a/tests/ui/pass/fn_poly_param_order.rs +++ b/tests/ui/pass/fn_poly_param_order.rs @@ -1,4 +1,5 @@ //@check-pass +//@rustc-env: THRUST_SOLVER=tests/thrust-pcsat-wrapper COAR_IMAGE=coar:latest fn select(a: T, b: U, c: V, which: i32) -> T { if which == 0 { diff --git a/tests/ui/pass/fn_poly_recursive.rs b/tests/ui/pass/fn_poly_recursive.rs index cfc7e2e4..da91d86d 100644 --- a/tests/ui/pass/fn_poly_recursive.rs +++ b/tests/ui/pass/fn_poly_recursive.rs @@ -1,6 +1,6 @@ //@check-pass //@compile-flags: -C debug-assertions=off - +//@rustc-env: THRUST_SOLVER=tests/thrust-pcsat-wrapper COAR_IMAGE=coar:latest fn repeat(n: i32, value: T) -> T { if n <= 1 { value diff --git a/tests/ui/pass/fn_poly_ref.rs b/tests/ui/pass/fn_poly_ref.rs index fae27aee..9b6bb6c1 100644 --- a/tests/ui/pass/fn_poly_ref.rs +++ b/tests/ui/pass/fn_poly_ref.rs @@ -1,4 +1,5 @@ //@check-pass +//@rustc-env: THRUST_SOLVER=tests/thrust-pcsat-wrapper COAR_IMAGE=coar:latest fn identity_ref(x: &T) -> &T { x diff --git a/tests/ui/pass/fn_poly_ref_ord.rs b/tests/ui/pass/fn_poly_ref_ord.rs index fefec5a0..7f722cd1 100644 --- a/tests/ui/pass/fn_poly_ref_ord.rs +++ b/tests/ui/pass/fn_poly_ref_ord.rs @@ -1,4 +1,5 @@ //@check-pass +//@rustc-env: THRUST_SOLVER=tests/thrust-pcsat-wrapper COAR_IMAGE=coar:latest fn lt(x: &T, y: &T) -> bool where T: Ord { x < y diff --git a/tests/ui/pass/fn_poly_unused_param.rs b/tests/ui/pass/fn_poly_unused_param.rs index e2ec90ed..aa75f5be 100644 --- a/tests/ui/pass/fn_poly_unused_param.rs +++ b/tests/ui/pass/fn_poly_unused_param.rs @@ -1,4 +1,5 @@ //@check-pass +//@rustc-env: THRUST_SOLVER=tests/thrust-pcsat-wrapper COAR_IMAGE=coar:latest fn project_first(triple: (T, U, V)) -> T { triple.0 diff --git a/tests/ui/pass/issue_108.rs b/tests/ui/pass/issue_108.rs index bbb7e86e..2c2544d5 100644 --- a/tests/ui/pass/issue_108.rs +++ b/tests/ui/pass/issue_108.rs @@ -1,6 +1,6 @@ //@check-pass //@compile-flags: -C debug-assertions=off - +//@rustc-env: THRUST_SOLVER=tests/thrust-pcsat-wrapper COAR_IMAGE=coar:latest fn apply T>(x: T, f: F) -> T { f(x) } diff --git a/tests/ui/pass/iterators/annot_range_loop.rs b/tests/ui/pass/iterators/annot_range_loop.rs index 2c09bff6..9b8afdc1 100644 --- a/tests/ui/pass/iterators/annot_range_loop.rs +++ b/tests/ui/pass/iterators/annot_range_loop.rs @@ -1,6 +1,6 @@ //@check-pass //@compile-flags: -C debug-assertions=off -//@rustc-env: THRUST_SOLVER=tests/thrust-pcsat-wrapper THRUST_SOLVER_TIMEOUT_SECS=60 +//@rustc-env: THRUST_SOLVER=tests/thrust-pcsat-wrapper THRUST_SOLVER_TIMEOUT_SECS=60 COAR_IMAGE=coar:latest #[thrust_macros::context] trait Iterator { diff --git a/tests/ui/pass/iterators/annot_range_next.rs b/tests/ui/pass/iterators/annot_range_next.rs index d0ecc0a4..52ba8e09 100644 --- a/tests/ui/pass/iterators/annot_range_next.rs +++ b/tests/ui/pass/iterators/annot_range_next.rs @@ -1,6 +1,6 @@ //@check-pass //@compile-flags: -C debug-assertions=off -//@rustc-env: THRUST_SOLVER=tests/thrust-pcsat-wrapper +//@rustc-env: THRUST_SOLVER=tests/thrust-pcsat-wrapper COAR_IMAGE=coar:latest #[thrust_macros::context] trait Iterator { diff --git a/tests/ui/pass/iterators/fixed_filter_loop_none.rs b/tests/ui/pass/iterators/fixed_filter_loop_none.rs index 393a1a30..3645a4f9 100644 --- a/tests/ui/pass/iterators/fixed_filter_loop_none.rs +++ b/tests/ui/pass/iterators/fixed_filter_loop_none.rs @@ -1,5 +1,6 @@ //@check-pass //@compile-flags: -C debug-assertions=off +//@rustc-env: THRUST_SOLVER=tests/thrust-pcsat-wrapper COAR_IMAGE=coar:latest struct Range { start: i64, diff --git a/tests/ui/pass/iterators/fixed_filter_next_some.rs b/tests/ui/pass/iterators/fixed_filter_next_some.rs index b7cd3f82..ff86e806 100644 --- a/tests/ui/pass/iterators/fixed_filter_next_some.rs +++ b/tests/ui/pass/iterators/fixed_filter_next_some.rs @@ -1,5 +1,6 @@ //@check-pass //@compile-flags: -C debug-assertions=off +//@rustc-env: THRUST_SOLVER=tests/thrust-pcsat-wrapper COAR_IMAGE=coar:latest struct Range { start: i64, diff --git a/tests/ui/pass/iterators/range.rs b/tests/ui/pass/iterators/range.rs index 7febdc60..8870a6ca 100644 --- a/tests/ui/pass/iterators/range.rs +++ b/tests/ui/pass/iterators/range.rs @@ -1,6 +1,6 @@ //@check-pass //@compile-flags: -C debug-assertions=off -//@rustc-env: THRUST_SOLVER_ARGS= +//@rustc-env: THRUST_SOLVER=tests/thrust-pcsat-wrapper THRUST_SOLVER_ARGS= COAR_IMAGE=coar:latest struct Range { start: i64, diff --git a/tests/ui/pass/loop_invariant_fn_param_closure.rs b/tests/ui/pass/loop_invariant_fn_param_closure.rs index 79d3928f..38b2b944 100644 --- a/tests/ui/pass/loop_invariant_fn_param_closure.rs +++ b/tests/ui/pass/loop_invariant_fn_param_closure.rs @@ -1,6 +1,6 @@ //@check-pass //@compile-flags: -C debug-assertions=off - +//@rustc-env: THRUST_SOLVER=tests/thrust-pcsat-wrapper COAR_IMAGE=coar:latest // A loop invariant refers to a closure parameter via `FnParam`, whose // `f.at_entry()` yields `Closure`. Here the invariant relates `acc` to the // entry closure's postcondition, from which the postcondition below is proven. diff --git a/tests/ui/pass/loop_invariant_generic.rs b/tests/ui/pass/loop_invariant_generic.rs index 7e31b002..779d313d 100644 --- a/tests/ui/pass/loop_invariant_generic.rs +++ b/tests/ui/pass/loop_invariant_generic.rs @@ -1,6 +1,6 @@ //@check-pass //@compile-flags: -C debug-assertions=off - +//@rustc-env: THRUST_SOLVER=tests/thrust-pcsat-wrapper COAR_IMAGE=coar:latest #[thrust_macros::requires(true)] #[thrust_macros::ensures(true)] #[thrust::trusted] diff --git a/tests/ui/pass/loop_invariant_generic_closure.rs b/tests/ui/pass/loop_invariant_generic_closure.rs index 73668ebe..faa4af25 100644 --- a/tests/ui/pass/loop_invariant_generic_closure.rs +++ b/tests/ui/pass/loop_invariant_generic_closure.rs @@ -1,6 +1,6 @@ //@check-pass //@compile-flags: -C debug-assertions=off - +//@rustc-env: THRUST_SOLVER=tests/thrust-pcsat-wrapper COAR_IMAGE=coar:latest #[thrust_macros::requires(true)] #[thrust_macros::ensures(true)] #[thrust::trusted] diff --git a/tests/ui/pass/loop_invariant_trait.rs b/tests/ui/pass/loop_invariant_trait.rs index 8acf26ee..be7e4101 100644 --- a/tests/ui/pass/loop_invariant_trait.rs +++ b/tests/ui/pass/loop_invariant_trait.rs @@ -1,6 +1,6 @@ //@check-pass //@compile-flags: -C debug-assertions=off - +//@rustc-env: THRUST_SOLVER=tests/thrust-pcsat-wrapper COAR_IMAGE=coar:latest #[thrust_macros::requires(true)] #[thrust_macros::ensures(true)] #[thrust::trusted] diff --git a/tests/ui/pass/option_inc.rs b/tests/ui/pass/option_inc.rs index aa0530b1..b8cff1f2 100644 --- a/tests/ui/pass/option_inc.rs +++ b/tests/ui/pass/option_inc.rs @@ -1,5 +1,6 @@ //@check-pass //@compile-flags: -C debug-assertions=off +//@rustc-env: THRUST_SOLVER=tests/thrust-pcsat-wrapper COAR_IMAGE=coar:latest fn maybe_inc(x: i32, do_it: bool) -> Option { if do_it { diff --git a/tests/ui/pass/option_loop.rs b/tests/ui/pass/option_loop.rs index ea3697fc..2c72418c 100644 --- a/tests/ui/pass/option_loop.rs +++ b/tests/ui/pass/option_loop.rs @@ -1,5 +1,6 @@ //@check-pass //@compile-flags: -C debug-assertions=off +//@rustc-env: THRUST_SOLVER=tests/thrust-pcsat-wrapper COAR_IMAGE=coar:latest fn main() { let mut opt = Some(5); diff --git a/tests/ui/pass/option_map.rs b/tests/ui/pass/option_map.rs index 3e6d05f2..2aec4d8f 100644 --- a/tests/ui/pass/option_map.rs +++ b/tests/ui/pass/option_map.rs @@ -1,6 +1,6 @@ //@check-pass //@compile-flags: -C debug-assertions=off -//@rustc-env: THRUST_SOLVER=tests/thrust-pcsat-wrapper +//@rustc-env: THRUST_SOLVER=tests/thrust-pcsat-wrapper COAR_IMAGE=coar:latest #[thrust::callable] fn check(opt: Option) { diff --git a/tests/ui/pass/option_mut.rs b/tests/ui/pass/option_mut.rs index 37f71215..f4905bd5 100644 --- a/tests/ui/pass/option_mut.rs +++ b/tests/ui/pass/option_mut.rs @@ -1,5 +1,6 @@ //@check-pass //@compile-flags: -C debug-assertions=off +//@rustc-env: THRUST_SOLVER=tests/thrust-pcsat-wrapper COAR_IMAGE=coar:latest fn main() { let mut m: Option = Some(1); diff --git a/tests/ui/pass/option_unwrap_or_else.rs b/tests/ui/pass/option_unwrap_or_else.rs index 5ebe703a..0f483f4a 100644 --- a/tests/ui/pass/option_unwrap_or_else.rs +++ b/tests/ui/pass/option_unwrap_or_else.rs @@ -1,6 +1,6 @@ //@check-pass //@compile-flags: -C debug-assertions=off -//@rustc-env: THRUST_SOLVER=tests/thrust-pcsat-wrapper +//@rustc-env: THRUST_SOLVER=tests/thrust-pcsat-wrapper COAR_IMAGE=coar:latest #[thrust::callable] fn check(o: Option, d: i32) { diff --git a/tests/ui/pass/refine_param_generic_adt.rs b/tests/ui/pass/refine_param_generic_adt.rs index 73f20579..c7ae4682 100644 --- a/tests/ui/pass/refine_param_generic_adt.rs +++ b/tests/ui/pass/refine_param_generic_adt.rs @@ -1,3 +1,4 @@ +//@rustc-env: THRUST_SOLVER=tests/thrust-pcsat-wrapper COAR_IMAGE=coar:latest //@check-pass pub enum Pair { diff --git a/tests/ui/pass/refine_param_nested_binder.rs b/tests/ui/pass/refine_param_nested_binder.rs index 9c49e3d7..f5e5a91a 100644 --- a/tests/ui/pass/refine_param_nested_binder.rs +++ b/tests/ui/pass/refine_param_nested_binder.rs @@ -1,3 +1,4 @@ +//@rustc-env: THRUST_SOLVER=tests/thrust-pcsat-wrapper COAR_IMAGE=coar:latest //@check-pass pub enum Pair { diff --git a/tests/ui/pass/refine_param_path_qualified.rs b/tests/ui/pass/refine_param_path_qualified.rs index ca8edd43..6b17a296 100644 --- a/tests/ui/pass/refine_param_path_qualified.rs +++ b/tests/ui/pass/refine_param_path_qualified.rs @@ -1,3 +1,4 @@ +//@rustc-env: THRUST_SOLVER=tests/thrust-pcsat-wrapper COAR_IMAGE=coar:latest //@check-pass pub enum Pair { diff --git a/tests/ui/pass/refine_sig_generic_adt.rs b/tests/ui/pass/refine_sig_generic_adt.rs index 9e1d7f39..4645cea3 100644 --- a/tests/ui/pass/refine_sig_generic_adt.rs +++ b/tests/ui/pass/refine_sig_generic_adt.rs @@ -1,3 +1,4 @@ +//@rustc-env: THRUST_SOLVER=tests/thrust-pcsat-wrapper COAR_IMAGE=coar:latest //@check-pass pub enum Pair { diff --git a/tests/ui/pass/result_mut.rs b/tests/ui/pass/result_mut.rs index 1a5e218c..133e9b01 100644 --- a/tests/ui/pass/result_mut.rs +++ b/tests/ui/pass/result_mut.rs @@ -1,5 +1,6 @@ //@check-pass //@compile-flags: -C debug-assertions=off +//@rustc-env: THRUST_SOLVER=tests/thrust-pcsat-wrapper COAR_IMAGE=coar:latest fn mutate_res(r: &mut Result) { match r { diff --git a/tests/ui/pass/result_struct.rs b/tests/ui/pass/result_struct.rs index 6e80f99f..f9a02443 100644 --- a/tests/ui/pass/result_struct.rs +++ b/tests/ui/pass/result_struct.rs @@ -1,5 +1,6 @@ //@check-pass //@compile-flags: -C debug-assertions=off +//@rustc-env: THRUST_SOLVER=tests/thrust-pcsat-wrapper COAR_IMAGE=coar:latest struct Point { x: i32, diff --git a/tests/ui/pass/slice_first_mut.rs b/tests/ui/pass/slice_first_mut.rs index 24ea5d93..0ff4ce88 100644 --- a/tests/ui/pass/slice_first_mut.rs +++ b/tests/ui/pass/slice_first_mut.rs @@ -1,5 +1,6 @@ //@check-pass //@compile-flags: -C debug-assertions=off +//@rustc-env: THRUST_SOLVER=tests/thrust-pcsat-wrapper COAR_IMAGE=coar:latest #[thrust::trusted] #[thrust_macros::requires(true)] diff --git a/tests/ui/pass/slice_last_mut.rs b/tests/ui/pass/slice_last_mut.rs index 92f94287..7c8f741e 100644 --- a/tests/ui/pass/slice_last_mut.rs +++ b/tests/ui/pass/slice_last_mut.rs @@ -1,5 +1,6 @@ //@check-pass //@compile-flags: -C debug-assertions=off +//@rustc-env: THRUST_SOLVER=tests/thrust-pcsat-wrapper COAR_IMAGE=coar:latest #[thrust::trusted] #[thrust_macros::requires(true)] diff --git a/tests/ui/pass/slice_methods.rs b/tests/ui/pass/slice_methods.rs index 70671165..f39109b7 100644 --- a/tests/ui/pass/slice_methods.rs +++ b/tests/ui/pass/slice_methods.rs @@ -1,5 +1,6 @@ //@check-pass //@compile-flags: -C debug-assertions=off +//@rustc-env: THRUST_SOLVER=tests/thrust-pcsat-wrapper COAR_IMAGE=coar:latest #[thrust::trusted] #[thrust_macros::requires(true)] diff --git a/tests/ui/pass/slice_methods_mut.rs b/tests/ui/pass/slice_methods_mut.rs index b7ef7bed..ff2743e2 100644 --- a/tests/ui/pass/slice_methods_mut.rs +++ b/tests/ui/pass/slice_methods_mut.rs @@ -1,5 +1,6 @@ //@check-pass //@compile-flags: -C debug-assertions=off +//@rustc-env: THRUST_SOLVER=tests/thrust-pcsat-wrapper COAR_IMAGE=coar:latest #[thrust::trusted] #[thrust_macros::requires(true)] diff --git a/tests/ui/pass/trait_assoc_type_spec.rs b/tests/ui/pass/trait_assoc_type_spec.rs index 97b63075..bb259597 100644 --- a/tests/ui/pass/trait_assoc_type_spec.rs +++ b/tests/ui/pass/trait_assoc_type_spec.rs @@ -1,6 +1,6 @@ //@check-pass //@compile-flags: -Adead_code -C debug-assertions=off -//@rustc-env: THRUST_SOLVER=tests/thrust-pcsat-wrapper +//@rustc-env: THRUST_SOLVER=tests/thrust-pcsat-wrapper COAR_IMAGE=coar:latest #[thrust_macros::context] trait Source { diff --git a/tests/ui/pass/traits/annot_simple_loop_self.rs b/tests/ui/pass/traits/annot_simple_loop_self.rs index 74671f6f..c1803b05 100644 --- a/tests/ui/pass/traits/annot_simple_loop_self.rs +++ b/tests/ui/pass/traits/annot_simple_loop_self.rs @@ -1,6 +1,6 @@ //@check-pass //@compile-flags: -C debug-assertions=off -//@rustc-env: THRUST_SOLVER=tests/thrust-pcsat-wrapper THRUST_SOLVER_TIMEOUT_SECS=60 +//@rustc-env: THRUST_SOLVER=tests/thrust-pcsat-wrapper THRUST_SOLVER_TIMEOUT_SECS=60 COAR_IMAGE=coar:latest #[thrust_macros::context] trait A { diff --git a/tests/ui/pass/traits/fold.rs b/tests/ui/pass/traits/fold.rs index 7935394e..ba1da174 100644 --- a/tests/ui/pass/traits/fold.rs +++ b/tests/ui/pass/traits/fold.rs @@ -1,6 +1,6 @@ //@check-pass //@compile-flags: -C debug-assertions=off -//@rustc-env: THRUST_SOLVER=tests/thrust-pcsat-wrapper THRUST_SOLVER_TIMEOUT_SECS=60 +//@rustc-env: THRUST_SOLVER=tests/thrust-pcsat-wrapper THRUST_SOLVER_TIMEOUT_SECS=60 COAR_IMAGE=coar:latest use thrust_models::{Model, exists, forall, model::Mut}; diff --git a/tests/ui/pass/traits/loop_unbound.rs b/tests/ui/pass/traits/loop_unbound.rs index 9677be02..9a2baa81 100644 --- a/tests/ui/pass/traits/loop_unbound.rs +++ b/tests/ui/pass/traits/loop_unbound.rs @@ -1,6 +1,6 @@ //@check-pass //@compile-flags: -C debug-assertions=off -//@rustc-env: THRUST_SOLVER=tests/thrust-pcsat-wrapper THRUST_SOLVER_TIMEOUT_SECS=60 +//@rustc-env: THRUST_SOLVER=tests/thrust-pcsat-wrapper THRUST_SOLVER_TIMEOUT_SECS=60 COAR_IMAGE=coar:latest #[thrust_macros::context] trait A { diff --git a/tests/ui/pass/traits/multi_params.rs b/tests/ui/pass/traits/multi_params.rs index 591ef705..c273e242 100644 --- a/tests/ui/pass/traits/multi_params.rs +++ b/tests/ui/pass/traits/multi_params.rs @@ -1,6 +1,6 @@ //@check-pass //@compile-flags: -C debug-assertions=off -//@rustc-env: THRUST_SOLVER=tests/thrust-pcsat-wrapper THRUST_SOLVER_TIMEOUT_SECS=60 +//@rustc-env: THRUST_SOLVER=tests/thrust-pcsat-wrapper THRUST_SOLVER_TIMEOUT_SECS=60 COAR_IMAGE=coar:latest #[thrust_macros::context] trait A { diff --git a/tests/ui/pass/traits/option_map.rs b/tests/ui/pass/traits/option_map.rs index e2b0a4b3..7ad1010f 100644 --- a/tests/ui/pass/traits/option_map.rs +++ b/tests/ui/pass/traits/option_map.rs @@ -1,6 +1,6 @@ //@check-pass //@compile-flags: -C debug-assertions=off -//@rustc-env: THRUST_SOLVER=tests/thrust-pcsat-wrapper THRUST_SOLVER_TIMEOUT_SECS=60 +//@rustc-env: THRUST_SOLVER=tests/thrust-pcsat-wrapper THRUST_SOLVER_TIMEOUT_SECS=60 COAR_IMAGE=coar:latest #[thrust_macros::requires( opt == None || thrust_models::exists(|i| opt == Some(i) && thrust_macros::pre!(f(i))) diff --git a/tests/ui/pass/traits/simple_loop.rs b/tests/ui/pass/traits/simple_loop.rs index 857ecdb3..d0848759 100644 --- a/tests/ui/pass/traits/simple_loop.rs +++ b/tests/ui/pass/traits/simple_loop.rs @@ -1,6 +1,6 @@ //@check-pass //@compile-flags: -C debug-assertions=off -//@rustc-env: THRUST_SOLVER=tests/thrust-pcsat-wrapper THRUST_SOLVER_TIMEOUT_SECS=60 +//@rustc-env: THRUST_SOLVER=tests/thrust-pcsat-wrapper THRUST_SOLVER_TIMEOUT_SECS=60 COAR_IMAGE=coar:latest #[thrust_macros::context] trait A { diff --git a/tests/ui/pass/traits/simple_loop_2int.rs b/tests/ui/pass/traits/simple_loop_2int.rs index 6edb3530..4f8d087f 100644 --- a/tests/ui/pass/traits/simple_loop_2int.rs +++ b/tests/ui/pass/traits/simple_loop_2int.rs @@ -1,6 +1,6 @@ //@check-pass //@compile-flags: -C debug-assertions=off -//@rustc-env: THRUST_SOLVER=tests/thrust-pcsat-wrapper THRUST_SOLVER_TIMEOUT_SECS=60 +//@rustc-env: THRUST_SOLVER=tests/thrust-pcsat-wrapper THRUST_SOLVER_TIMEOUT_SECS=60 COAR_IMAGE=coar:latest #[thrust_macros::context] trait A { diff --git a/tests/ui/pass/traits/simple_loop_call.rs b/tests/ui/pass/traits/simple_loop_call.rs index 39115fd8..0936bd95 100644 --- a/tests/ui/pass/traits/simple_loop_call.rs +++ b/tests/ui/pass/traits/simple_loop_call.rs @@ -1,6 +1,6 @@ //@check-pass //@compile-flags: -C debug-assertions=off -//@rustc-env: THRUST_SOLVER=tests/thrust-pcsat-wrapper THRUST_SOLVER_TIMEOUT_SECS=60 +//@rustc-env: THRUST_SOLVER=tests/thrust-pcsat-wrapper THRUST_SOLVER_TIMEOUT_SECS=60 COAR_IMAGE=coar:latest #[thrust_macros::context] trait A { diff --git a/tests/ui/pass/traits/simple_loop_call_multi.rs b/tests/ui/pass/traits/simple_loop_call_multi.rs index 92254399..c17e2caf 100644 --- a/tests/ui/pass/traits/simple_loop_call_multi.rs +++ b/tests/ui/pass/traits/simple_loop_call_multi.rs @@ -1,6 +1,6 @@ //@check-pass //@compile-flags: -C debug-assertions=off -//@rustc-env: THRUST_SOLVER=tests/thrust-pcsat-wrapper THRUST_SOLVER_TIMEOUT_SECS=60 +//@rustc-env: THRUST_SOLVER=tests/thrust-pcsat-wrapper THRUST_SOLVER_TIMEOUT_SECS=60 COAR_IMAGE=coar:latest #[thrust_macros::context] trait A { diff --git a/tests/ui/pass/traits/simple_loop_self.rs b/tests/ui/pass/traits/simple_loop_self.rs index 0c026e06..4a38e32f 100644 --- a/tests/ui/pass/traits/simple_loop_self.rs +++ b/tests/ui/pass/traits/simple_loop_self.rs @@ -1,6 +1,6 @@ //@check-pass //@compile-flags: -C debug-assertions=off -//@rustc-env: THRUST_SOLVER=tests/thrust-pcsat-wrapper THRUST_SOLVER_TIMEOUT_SECS=60 +//@rustc-env: THRUST_SOLVER=tests/thrust-pcsat-wrapper THRUST_SOLVER_TIMEOUT_SECS=60 COAR_IMAGE=coar:latest #[thrust_macros::context] trait A { diff --git a/tests/ui/pass/traits/simple_loop_self_mut.rs b/tests/ui/pass/traits/simple_loop_self_mut.rs index 53a69e22..3bd731bd 100644 --- a/tests/ui/pass/traits/simple_loop_self_mut.rs +++ b/tests/ui/pass/traits/simple_loop_self_mut.rs @@ -1,6 +1,6 @@ //@check-pass //@compile-flags: -C debug-assertions=off -//@rustc-env: THRUST_SOLVER=tests/thrust-pcsat-wrapper THRUST_SOLVER_TIMEOUT_SECS=60 +//@rustc-env: THRUST_SOLVER=tests/thrust-pcsat-wrapper THRUST_SOLVER_TIMEOUT_SECS=60 COAR_IMAGE=coar:latest #[thrust_macros::context] trait A { diff --git a/tests/ui/pass/traits/two_loops.rs b/tests/ui/pass/traits/two_loops.rs index 198354b1..9de02ee2 100644 --- a/tests/ui/pass/traits/two_loops.rs +++ b/tests/ui/pass/traits/two_loops.rs @@ -1,6 +1,6 @@ //@check-pass //@compile-flags: -C debug-assertions=off -//@rustc-env: THRUST_SOLVER=tests/thrust-pcsat-wrapper THRUST_SOLVER_TIMEOUT_SECS=60 +//@rustc-env: THRUST_SOLVER=tests/thrust-pcsat-wrapper THRUST_SOLVER_TIMEOUT_SECS=60 COAR_IMAGE=coar:latest #[thrust_macros::context] trait A { diff --git a/tests/ui/pass/vec_2.rs b/tests/ui/pass/vec_2.rs index 419a3516..32e58962 100644 --- a/tests/ui/pass/vec_2.rs +++ b/tests/ui/pass/vec_2.rs @@ -1,5 +1,6 @@ //@check-pass //@compile-flags: -C debug-assertions=off +//@rustc-env: THRUST_SOLVER=tests/thrust-pcsat-wrapper COAR_IMAGE=coar:latest fn main() { let mut v = Vec::new(); From 5b2d3bfef0e2ed19dc6a4099b3aa7b587ef22c9f Mon Sep 17 00:00:00 2001 From: coeff-aij <175928954+coeff-aij@users.noreply.github.com> Date: Sun, 16 Aug 2026 23:16:14 +0900 Subject: [PATCH 094/142] fix: include caller in generic type cache keys --- src/analyze.rs | 27 +++++++++++++++++++++------ src/analyze/annot_fn.rs | 7 +++++-- 2 files changed, 26 insertions(+), 8 deletions(-) diff --git a/src/analyze.rs b/src/analyze.rs index 1667bc25..f29cc4d7 100644 --- a/src/analyze.rs +++ b/src/analyze.rs @@ -176,7 +176,7 @@ struct DeferredDefTy<'tcx> { // the def that provides the spec (`expected_ty`). this is different from a key in defs when // the def is an extern_spec_fn (then it is the extern_spec_fn wrapper carrying the contract). local_def_id: LocalDefId, - cache: Rc, rty::RefinedType>>>, + cache: Rc, rty::RefinedType>>>, mode: DeferredDefMode, } @@ -184,10 +184,16 @@ struct DeferredDefTy<'tcx> { struct GenericDefTy<'tcx> { // this is different from a key in defs when the def is extern_spec_fn local_def_id: LocalDefId, - cache: Rc, rty::RefinedType>>>, + cache: Rc, rty::RefinedType>>>, rty: Option, } +#[derive(Debug, Clone, Copy, Hash, PartialEq, Eq)] +struct InstantiationKey<'tcx> { + generic_args: mir_ty::GenericArgsRef<'tcx>, + caller_def_id: DefId, +} + #[derive(Debug, Clone)] enum DefTy<'tcx> { Concrete(rty::RefinedType), @@ -483,6 +489,7 @@ impl<'tcx> Analyzer<'tcx> { &self, def_id: DefId, generic_args: mir_ty::GenericArgsRef<'tcx>, + caller_def_id: DefId, ) -> Option { let type_builder = TypeBuilder::new( self.tcx, @@ -492,10 +499,14 @@ impl<'tcx> Analyzer<'tcx> { self.closure_type_params.clone(), self.system.clone(), ); + let key = InstantiationKey { + generic_args, + caller_def_id, + }; let mut def_ty = match self.defs.get(&def_id)? { DefTy::Concrete(rty) => rty.clone(), - DefTy::Generic(generic) => generic.cache.borrow().get(&generic_args)?.clone(), - DefTy::Deferred(deferred) => deferred.cache.borrow().get(&generic_args)?.clone(), + DefTy::Generic(generic) => generic.cache.borrow().get(&key)?.clone(), + DefTy::Deferred(deferred) => deferred.cache.borrow().get(&key)?.clone(), }; def_ty.instantiate_ty_params( generic_args @@ -569,7 +580,11 @@ impl<'tcx> Analyzer<'tcx> { ), }; - if let Some(rty) = instantiated_ty_cache.borrow().get(&generic_args) { + let key = InstantiationKey { + generic_args, + caller_def_id, + }; + if let Some(rty) = instantiated_ty_cache.borrow().get(&key) { return Some(rty.clone()); } @@ -581,7 +596,7 @@ impl<'tcx> Analyzer<'tcx> { let expected = analyzer.expected_ty(); instantiated_ty_cache .borrow_mut() - .insert(generic_args, expected.clone()); + .insert(key, expected.clone()); tracing::info!(?def_id, rty = %expected.display(), ?generic_args, "deferred def"); if deferred_ty_mode.is_some_and(|mode| mode.should_analyze()) { diff --git a/src/analyze/annot_fn.rs b/src/analyze/annot_fn.rs index 94a10834..16c6dc89 100644 --- a/src/analyze/annot_fn.rs +++ b/src/analyze/annot_fn.rs @@ -510,8 +510,11 @@ impl<'a, 'tcx> AnnotFnTranslator<'a, 'tcx> { } return None; }; - self.analyzer - .known_function_ty_with_args(*def_id, self.tcx.mk_args(args.as_closure().parent_args())) + self.analyzer.known_function_ty_with_args( + *def_id, + self.tcx.mk_args(args.as_closure().parent_args()), + self.type_builder.owner_fn_id(), + ) } fn register_forall_pred(&self, forall_pred: chc::ForallPred) { From e18fba196e15248a3dde171e3fc1ad8f98261db5 Mon Sep 17 00:00:00 2001 From: coeff-aij <175928954+coeff-aij@users.noreply.github.com> Date: Sun, 16 Aug 2026 23:26:19 +0900 Subject: [PATCH 095/142] improve forall sort type formatting --- src/rty.rs | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/src/rty.rs b/src/rty.rs index 7f38a92c..a60d3363 100644 --- a/src/rty.rs +++ b/src/rty.rs @@ -824,7 +824,11 @@ where D: pretty::DocAllocator<'a, termcolor::ColorSpec>, { fn pretty(self, allocator: &'a D) -> pretty::DocBuilder<'a, D, termcolor::ColorSpec> { - self.type_param_idx.pretty(allocator) + self.type_param_idx + .pretty(allocator) + .append(allocator.text("(ForallSortIdx=")) + .append(self.forall_sort_idx.pretty(allocator)) + .append(allocator.text(")")) } } From 7993e4bd2cc17fc7031c98e485671aabb024cd2c Mon Sep 17 00:00:00 2001 From: coeff-aij <175928954+coeff-aij@users.noreply.github.com> Date: Sun, 16 Aug 2026 23:27:58 +0900 Subject: [PATCH 096/142] test: cover forall sort type formatting --- src/rty.rs | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/src/rty.rs b/src/rty.rs index a60d3363..1f638474 100644 --- a/src/rty.rs +++ b/src/rty.rs @@ -2142,3 +2142,17 @@ where s2 }) } + +#[cfg(test)] +mod tests { + use super::{ParamType, TypeParamIdx}; + use crate::chc::ForallSortIdx; + use crate::pretty::PrettyDisplayExt as _; + + #[test] + fn param_type_display_includes_forall_sort() { + let param = ParamType::new(TypeParamIdx::from(0_usize), ForallSortIdx::from(2_usize)); + + assert_eq!(param.display().to_string(), "T0(ForallSortIdx=a2)"); + } +} From ceb4d03197bb9c4f84d8da4fb252069bf86df674 Mon Sep 17 00:00:00 2001 From: coeff-aij <175928954+coeff-aij@users.noreply.github.com> Date: Mon, 17 Aug 2026 00:11:32 +0900 Subject: [PATCH 097/142] fix: handle singleton refinement values --- src/rty.rs | 18 ++++++++++++++---- src/rty/clause_builder.rs | 20 ++++++++++++-------- 2 files changed, 26 insertions(+), 12 deletions(-) diff --git a/src/rty.rs b/src/rty.rs index 1f638474..8a7afccc 100644 --- a/src/rty.rs +++ b/src/rty.rs @@ -1648,6 +1648,7 @@ impl Refinement { pub fn instantiate(self) -> Instantiator { Instantiator { value_var: None, + value_term: None, existentials: HashMap::new(), refinement: self, } @@ -1668,6 +1669,7 @@ impl Refinement { #[derive(Debug, Clone)] pub struct Instantiator { value_var: Option, + value_term: Option>, existentials: HashMap, refinement: Refinement, } @@ -1678,6 +1680,11 @@ impl Instantiator { self } + pub fn value_term(&mut self, value_term: chc::Term) -> &mut Self { + self.value_term = Some(value_term); + self + } + pub fn existential(&mut self, v: ExistentialVarIdx, value: T) -> &mut Self { self.existentials.insert(v, value); self @@ -1689,13 +1696,16 @@ impl Instantiator { { let Instantiator { value_var, + value_term, existentials, refinement, } = self; - refinement.body.map_var(move |v| match v { - RefinedTypeVar::Value => value_var.clone().unwrap(), - RefinedTypeVar::Existential(v) => existentials[&v].clone(), - RefinedTypeVar::Free(v) => v, + refinement.body.subst_var(move |v| match v { + RefinedTypeVar::Value => value_term + .clone() + .unwrap_or_else(|| chc::Term::var(value_var.clone().unwrap())), + RefinedTypeVar::Existential(v) => chc::Term::var(existentials[&v].clone()), + RefinedTypeVar::Free(v) => chc::Term::var(v), }) } } diff --git a/src/rty/clause_builder.rs b/src/rty/clause_builder.rs index 68c090c5..de2d8331 100644 --- a/src/rty/clause_builder.rs +++ b/src/rty/clause_builder.rs @@ -24,10 +24,14 @@ pub trait ClauseBuilderExt { impl ClauseBuilderExt for chc::ClauseBuilder { fn with_value_var<'a, T>(&'a mut self, ty: &Type) -> RefinementClauseBuilder<'a> { let ty_sort = ty.to_sort(); - let value_var = (!ty_sort.is_singleton()).then(|| self.add_var(ty_sort)); + let value_term = if ty_sort.is_singleton() { + Some(chc::Term::default_for(&ty_sort)) + } else { + Some(chc::Term::var(self.add_var(ty_sort))) + }; RefinementClauseBuilder { builder: self, - value_var, + value_term, } } @@ -38,7 +42,7 @@ impl ClauseBuilderExt for chc::ClauseBuilder { let value_var = self.find_mapped_var(v); RefinementClauseBuilder { builder: self, - value_var, + value_term: value_var.map(chc::Term::var), } } } @@ -49,7 +53,7 @@ impl ClauseBuilderExt for chc::ClauseBuilder { /// will take care of mapping the variables appropriately. pub struct RefinementClauseBuilder<'a> { builder: &'a mut chc::ClauseBuilder, - value_var: Option, + value_term: Option>, } impl<'a> RefinementClauseBuilder<'a> { @@ -68,8 +72,8 @@ impl<'a> RefinementClauseBuilder<'a> { let tv = self.builder.add_var(sort); instantiator.existential(ev, tv); } - if let Some(value_var) = self.value_var { - instantiator.value_var(value_var); + if let Some(value_term) = &self.value_term { + instantiator.value_term(value_term.clone()); } let chc::Body { atoms, formula } = instantiator.instantiate(); for atom in atoms { @@ -89,8 +93,8 @@ impl<'a> RefinementClauseBuilder<'a> { let mut instantiator = refinement .map_free_var(|v| self.builder.mapped_var(v)) .instantiate(); - if let Some(value_var) = self.value_var { - instantiator.value_var(value_var); + if let Some(value_term) = &self.value_term { + instantiator.value_term(value_term.clone()); } let chc::Body { atoms, formula } = instantiator.instantiate(); let mut cs = atoms From 5eef7177ed48932cc9b7be3e91f0b160240e81c4 Mon Sep 17 00:00:00 2001 From: coeff-aij <175928954+coeff-aij@users.noreply.github.com> Date: Mon, 17 Aug 2026 00:23:20 +0900 Subject: [PATCH 098/142] fix: add forall sort default values --- src/chc.rs | 36 ++++++++++++++++++++++++++++++++++-- src/chc/format_context.rs | 1 + src/chc/smtlib2.rs | 8 ++++++++ src/chc/unbox.rs | 7 ++++++- src/rty.rs | 1 + 5 files changed, 50 insertions(+), 3 deletions(-) diff --git a/src/chc.rs b/src/chc.rs index c2c7ebf2..025b1e73 100644 --- a/src/chc.rs +++ b/src/chc.rs @@ -545,6 +545,7 @@ impl SeqConcatTerm { #[derive(Debug, Clone, PartialEq, Eq, Hash)] pub enum Term { Null, + ForallDefault(ForallSortIdx), Var(V), Bool(bool), Int(i64), @@ -574,6 +575,7 @@ where fn pretty(self, allocator: &'a D) -> pretty::DocBuilder<'a, D, termcolor::ColorSpec> { match self { Term::Null => allocator.text("null"), + Term::ForallDefault(idx) => allocator.text(format!("default_{idx}")), Term::Var(var) => allocator.text(format!("{var:?}")), Term::Int(n) => allocator.as_string(n), Term::Bool(b) => allocator.as_string(b), @@ -659,6 +661,7 @@ impl Term { fn subst_var_impl(self, mut f: Box Term + '_>) -> Term { match self { Term::Null => Term::Null, + Term::ForallDefault(idx) => Term::ForallDefault(idx), Term::Var(v) => f(v), Term::Bool(b) => Term::Bool(b), Term::Int(n) => Term::Int(n), @@ -707,6 +710,7 @@ impl Term { { match self { Term::Null => Sort::null(), + Term::ForallDefault(idx) => Sort::forall(*idx), Term::Var(v) => var_sort(v), Term::Bool(_) => Sort::bool(), Term::Int(_) => Sort::int(), @@ -739,6 +743,7 @@ impl Term { match self { Term::Var(v) => Box::new(std::iter::once(v)), Term::Null + | Term::ForallDefault(_) | Term::Bool(_) | Term::Int(_) | Term::String(_) @@ -788,6 +793,7 @@ impl Term { pub fn default_for(sort: &Sort) -> Self { match sort { Sort::Null => Term::Null, + Sort::Forall(idx) => Term::ForallDefault(*idx), Sort::Int => Term::Int(0), Sort::Bool => Term::Bool(false), Sort::String => Term::String(String::new()), @@ -798,8 +804,8 @@ impl Term { ), Sort::Tuple(ts) => Term::Tuple(ts.iter().map(Self::default_for).collect()), Sort::Array(i, e) => Term::ArrayEmpty((**i).clone(), (**e).clone()), - // TODO: defaults for Datatype and Param and Forall. - Sort::Datatype(_) | Sort::Param(_) | Sort::Forall(_) => { + // TODO: defaults for Datatype and Param. + Sort::Datatype(_) | Sort::Param(_) => { unimplemented!("no default value for sort {sort:?}") } } @@ -2397,3 +2403,29 @@ impl System { Config::from_env().check_sat(system.smtlib2()) } } + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn declares_forall_default_once() { + let mut system = System::default(); + let idx = system.new_forall_sort(); + let default = Term::default_for(&Sort::forall(idx)); + let body = Atom::new( + Pred::Known(KnownPred::EQUAL), + vec![default, Term::var(0usize.into())], + ); + system.push_clause(Clause { + vars: [Sort::forall(idx)].into_iter().collect(), + head: Atom::new(Pred::UserDefined(UserDefinedPred::new("p".into())), vec![]), + body: body.into(), + debug_info: DebugInfo::default(), + }); + + let smt = system.smtlib2().to_string(); + assert_eq!(smt.matches("(declare-const default_a0 a0)").count(), 1); + assert_eq!(smt.matches("default_a0").count(), 2); + } +} diff --git a/src/chc/format_context.rs b/src/chc/format_context.rs index a4e8dce5..7a007c0f 100644 --- a/src/chc/format_context.rs +++ b/src/chc/format_context.rs @@ -29,6 +29,7 @@ fn term_sorts(clause: &chc::Clause, t: &chc::Term, sorts: &mut BTreeSet {} + chc::Term::ForallDefault(_) => {} chc::Term::Var(_) => {} chc::Term::Bool(_) => {} chc::Term::Int(_) => {} diff --git a/src/chc/smtlib2.rs b/src/chc/smtlib2.rs index bafa86ad..63b2e954 100644 --- a/src/chc/smtlib2.rs +++ b/src/chc/smtlib2.rs @@ -103,6 +103,7 @@ impl<'ctx, 'a> std::fmt::Display for Term<'ctx, 'a> { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { match self.inner { chc::Term::Null => write!(f, "null"), + chc::Term::ForallDefault(idx) => write!(f, "default_{idx}"), chc::Term::Var(v) => write!(f, "{}", v), chc::Term::Int(i) => write!(f, "{}", i), chc::Term::Bool(b) => write!(f, "{}", b), @@ -700,6 +701,13 @@ impl<'a> std::fmt::Display for System<'a> { for forall_sort_idx in &self.inner.forall_sorts { writeln!(f, "(declare-forall-sort {})\n", forall_sort_idx)?; } + for forall_sort_idx in &self.inner.forall_sorts { + writeln!( + f, + "(declare-const default_{} {})\n", + forall_sort_idx, forall_sort_idx + )?; + } for pred in &self.inner.forall_pred_vars { writeln!(f, "{}\n", ForallPredDef::new(&self.ctx, pred))?; diff --git a/src/chc/unbox.rs b/src/chc/unbox.rs index 208cb481..9a5d91c5 100644 --- a/src/chc/unbox.rs +++ b/src/chc/unbox.rs @@ -11,7 +11,12 @@ fn unbox_seq_concat_term(t: SeqConcatTerm) -> SeqConcatTerm { fn unbox_term(term: Term) -> Term { match term { - Term::Var(_) | Term::Bool(_) | Term::Int(_) | Term::String(_) | Term::Null => term, + Term::Var(_) + | Term::Bool(_) + | Term::Int(_) + | Term::String(_) + | Term::Null + | Term::ForallDefault(_) => term, Term::Box(t) => unbox_term(*t), Term::Mut(t1, t2) => Term::Mut(Box::new(unbox_term(*t1)), Box::new(unbox_term(*t2))), Term::BoxCurrent(t) => unbox_term(*t), diff --git a/src/rty.rs b/src/rty.rs index 8a7afccc..a98652a5 100644 --- a/src/rty.rs +++ b/src/rty.rs @@ -2091,6 +2091,7 @@ fn subst_ty_params_in_formula(formula: &mut chc::Formula, subst: &TypeP fn subst_ty_params_in_term(term: &mut chc::Term, subst: &TypeParamSubst) { match term { chc::Term::Null + | chc::Term::ForallDefault(_) | chc::Term::Var(_) | chc::Term::Bool(_) | chc::Term::Int(_) From 015bda28e6bf8dd487d808624cb4a2dcec124368 Mon Sep 17 00:00:00 2001 From: coeff-aij <175928954+coeff-aij@users.noreply.github.com> Date: Mon, 17 Aug 2026 00:24:42 +0900 Subject: [PATCH 099/142] fix: handle unresolved trait method calls --- src/analyze.rs | 8 ++++++++ src/analyze/basic_block.rs | 23 +++++++++++++++++++++++ 2 files changed, 31 insertions(+) diff --git a/src/analyze.rs b/src/analyze.rs index f29cc4d7..63b935d6 100644 --- a/src/analyze.rs +++ b/src/analyze.rs @@ -976,6 +976,14 @@ impl<'tcx> Analyzer<'tcx> { out } + /// Whether the given `def_id` corresponds to a method of a trait. + pub fn is_trait_method(&self, def_id: DefId) -> bool { + self.tcx + .opt_associated_item(def_id) + .and_then(|item| item.trait_container(self.tcx)) + .is_some() + } + /// Whether the given `def_id` corresponds to a method of one of the `Fn` traits. fn is_fn_trait_method(&self, def_id: DefId) -> bool { self.tcx diff --git a/src/analyze/basic_block.rs b/src/analyze/basic_block.rs index e08b3a13..b5d592fd 100644 --- a/src/analyze/basic_block.rs +++ b/src/analyze/basic_block.rs @@ -954,6 +954,10 @@ impl<'tcx, 'ctx> Analyzer<'tcx, 'ctx> { } }; if resolved_def_id == def_id { + if self.ctx.is_trait_method(def_id) { + tracing::debug!(?def_id, ?args, "using abstract trait method type"); + return self.abstract_callable_ty(def_id, args); + } panic!( "unknown def (and not resolved): {:?}, args: {:?}", def_id, args @@ -972,6 +976,25 @@ impl<'tcx, 'ctx> Analyzer<'tcx, 'ctx> { def_ty.ty } + fn abstract_callable_ty( + &self, + def_id: DefId, + args: mir_ty::GenericArgsRef<'tcx>, + ) -> rty::Type { + let sig = self + .tcx + .fn_sig(def_id) + .instantiate(self.tcx, args) + .skip_binder(); + let params = sig + .inputs() + .iter() + .map(|ty| rty::RefinedType::unrefined(self.type_builder.build(*ty)).vacuous()) + .collect(); + let ret = rty::RefinedType::unrefined(self.type_builder.build(sig.output())).vacuous(); + rty::FunctionType::new(params, ret).into() + } + fn type_call(&mut self, func: Operand<'tcx>, args: I, expected_ret: &rty::RefinedType) where I: IntoIterator>, From 6457860ee1ebe91b838595b2cf5ebd81ac9a5498 Mon Sep 17 00:00:00 2001 From: coeff-aij <175928954+coeff-aij@users.noreply.github.com> Date: Mon, 17 Aug 2026 00:47:09 +0900 Subject: [PATCH 100/142] test: specify trait update invariant --- tests/ui/fail/loop_invariant_trait_self.rs | 1 + tests/ui/pass/loop_invariant_trait_self.rs | 1 + 2 files changed, 2 insertions(+) diff --git a/tests/ui/fail/loop_invariant_trait_self.rs b/tests/ui/fail/loop_invariant_trait_self.rs index 914a9214..15dc80ad 100644 --- a/tests/ui/fail/loop_invariant_trait_self.rs +++ b/tests/ui/fail/loop_invariant_trait_self.rs @@ -14,6 +14,7 @@ trait Gauge { #[thrust_macros::predicate] fn invariant(x: i32) -> bool; + #[thrust_macros::ensures(Self::invariant(result))] fn update(&mut self) -> i32; #[thrust_macros::invariant_context] diff --git a/tests/ui/pass/loop_invariant_trait_self.rs b/tests/ui/pass/loop_invariant_trait_self.rs index 5047710b..ce3dd329 100644 --- a/tests/ui/pass/loop_invariant_trait_self.rs +++ b/tests/ui/pass/loop_invariant_trait_self.rs @@ -14,6 +14,7 @@ trait Gauge { #[thrust_macros::predicate] fn invariant(x: i32) -> bool; + #[thrust_macros::ensures(Self::invariant(result))] fn update(&mut self) -> i32; #[thrust_macros::invariant_context] From 1d814ff5c2b83e1b5ebc0036e756f9de37adf4cc Mon Sep 17 00:00:00 2001 From: coeff-aij <175928954+coeff-aij@users.noreply.github.com> Date: Mon, 17 Aug 2026 01:20:28 +0900 Subject: [PATCH 101/142] tests: configure solver for remaining polymorphic cases --- tests/ui/fail/adt_poly_fn_poly.rs | 1 + tests/ui/fail/closure_mut_param.rs | 1 + tests/ui/fail/fn_poly_double_nested.rs | 1 + tests/ui/fail/fn_poly_nested_calls.rs | 1 + tests/ui/fail/loop_invariant_trait_self.rs | 2 +- tests/ui/fail/trait_param.rs | 1 + tests/ui/pass/adt_poly_fn_poly.rs | 1 + tests/ui/pass/closure_mut_param.rs | 1 + tests/ui/pass/fn_poly_double_nested.rs | 1 + tests/ui/pass/fn_poly_nested_calls.rs | 1 + tests/ui/pass/loop_invariant_trait_self.rs | 2 +- tests/ui/pass/trait_param.rs | 1 + 12 files changed, 12 insertions(+), 2 deletions(-) diff --git a/tests/ui/fail/adt_poly_fn_poly.rs b/tests/ui/fail/adt_poly_fn_poly.rs index e5c3d3ac..98490892 100644 --- a/tests/ui/fail/adt_poly_fn_poly.rs +++ b/tests/ui/fail/adt_poly_fn_poly.rs @@ -1,4 +1,5 @@ //@error-in-other-file: Unsat +//@rustc-env: THRUST_SOLVER=tests/thrust-pcsat-wrapper COAR_IMAGE=coar:latest pub enum X { A(T), diff --git a/tests/ui/fail/closure_mut_param.rs b/tests/ui/fail/closure_mut_param.rs index 799c5474..57074578 100644 --- a/tests/ui/fail/closure_mut_param.rs +++ b/tests/ui/fail/closure_mut_param.rs @@ -1,4 +1,5 @@ //@error-in-other-file: Unsat +//@rustc-env: THRUST_SOLVER=tests/thrust-pcsat-wrapper COAR_IMAGE=coar:latest fn next(f: &mut F) where F: Fn() { f(); diff --git a/tests/ui/fail/fn_poly_double_nested.rs b/tests/ui/fail/fn_poly_double_nested.rs index 15040c52..1daf5d24 100644 --- a/tests/ui/fail/fn_poly_double_nested.rs +++ b/tests/ui/fail/fn_poly_double_nested.rs @@ -1,4 +1,5 @@ //@error-in-other-file: Unsat +//@rustc-env: THRUST_SOLVER=tests/thrust-pcsat-wrapper COAR_IMAGE=coar:latest fn id(x: T) -> T { x diff --git a/tests/ui/fail/fn_poly_nested_calls.rs b/tests/ui/fail/fn_poly_nested_calls.rs index 609f1362..6c147a9f 100644 --- a/tests/ui/fail/fn_poly_nested_calls.rs +++ b/tests/ui/fail/fn_poly_nested_calls.rs @@ -1,4 +1,5 @@ //@error-in-other-file: Unsat +//@rustc-env: THRUST_SOLVER=tests/thrust-pcsat-wrapper COAR_IMAGE=coar:latest fn id(x: T) -> T { x diff --git a/tests/ui/fail/loop_invariant_trait_self.rs b/tests/ui/fail/loop_invariant_trait_self.rs index 15dc80ad..a2a5d104 100644 --- a/tests/ui/fail/loop_invariant_trait_self.rs +++ b/tests/ui/fail/loop_invariant_trait_self.rs @@ -1,6 +1,6 @@ //@error-in-other-file: Unsat //@compile-flags: -C debug-assertions=off -//@rustc-env: THRUST_SOLVER=tests/thrust-pcsat-wrapper THRUST_SOLVER_TIMEOUT_SECS=60 +//@rustc-env: THRUST_SOLVER=tests/thrust-pcsat-wrapper THRUST_SOLVER_TIMEOUT_SECS=60 COAR_IMAGE=coar:latest #[thrust_macros::requires(true)] #[thrust_macros::ensures(true)] diff --git a/tests/ui/fail/trait_param.rs b/tests/ui/fail/trait_param.rs index b0d60811..8c2a630d 100644 --- a/tests/ui/fail/trait_param.rs +++ b/tests/ui/fail/trait_param.rs @@ -1,5 +1,6 @@ //@error-in-other-file: Unsat //@compile-flags: -C debug-assertions=off +//@rustc-env: THRUST_SOLVER=tests/thrust-pcsat-wrapper COAR_IMAGE=coar:latest trait BoolLike { fn truthy(&self) -> bool; diff --git a/tests/ui/pass/adt_poly_fn_poly.rs b/tests/ui/pass/adt_poly_fn_poly.rs index 10887264..2f89305a 100644 --- a/tests/ui/pass/adt_poly_fn_poly.rs +++ b/tests/ui/pass/adt_poly_fn_poly.rs @@ -1,4 +1,5 @@ //@check-pass +//@rustc-env: THRUST_SOLVER=tests/thrust-pcsat-wrapper COAR_IMAGE=coar:latest pub enum X { A(T), diff --git a/tests/ui/pass/closure_mut_param.rs b/tests/ui/pass/closure_mut_param.rs index fdfe826e..5ada9d12 100644 --- a/tests/ui/pass/closure_mut_param.rs +++ b/tests/ui/pass/closure_mut_param.rs @@ -1,4 +1,5 @@ //@check-pass +//@rustc-env: THRUST_SOLVER=tests/thrust-pcsat-wrapper COAR_IMAGE=coar:latest fn next(f: &mut F) where F: Fn() { f(); diff --git a/tests/ui/pass/fn_poly_double_nested.rs b/tests/ui/pass/fn_poly_double_nested.rs index 5c9b0547..834cc4ff 100644 --- a/tests/ui/pass/fn_poly_double_nested.rs +++ b/tests/ui/pass/fn_poly_double_nested.rs @@ -1,4 +1,5 @@ //@check-pass +//@rustc-env: THRUST_SOLVER=tests/thrust-pcsat-wrapper COAR_IMAGE=coar:latest fn id(x: T) -> T { x diff --git a/tests/ui/pass/fn_poly_nested_calls.rs b/tests/ui/pass/fn_poly_nested_calls.rs index b2bef0b4..28a34849 100644 --- a/tests/ui/pass/fn_poly_nested_calls.rs +++ b/tests/ui/pass/fn_poly_nested_calls.rs @@ -1,4 +1,5 @@ //@check-pass +//@rustc-env: THRUST_SOLVER=tests/thrust-pcsat-wrapper COAR_IMAGE=coar:latest fn id(x: T) -> T { x diff --git a/tests/ui/pass/loop_invariant_trait_self.rs b/tests/ui/pass/loop_invariant_trait_self.rs index ce3dd329..693fad93 100644 --- a/tests/ui/pass/loop_invariant_trait_self.rs +++ b/tests/ui/pass/loop_invariant_trait_self.rs @@ -1,6 +1,6 @@ //@check-pass //@compile-flags: -C debug-assertions=off -//@rustc-env: THRUST_SOLVER=tests/thrust-pcsat-wrapper THRUST_SOLVER_TIMEOUT_SECS=60 +//@rustc-env: THRUST_SOLVER=tests/thrust-pcsat-wrapper THRUST_SOLVER_TIMEOUT_SECS=60 COAR_IMAGE=coar:latest #[thrust_macros::requires(true)] #[thrust_macros::ensures(true)] diff --git a/tests/ui/pass/trait_param.rs b/tests/ui/pass/trait_param.rs index 7cc41796..8b167dec 100644 --- a/tests/ui/pass/trait_param.rs +++ b/tests/ui/pass/trait_param.rs @@ -1,5 +1,6 @@ //@check-pass //@compile-flags: -C debug-assertions=off +//@rustc-env: THRUST_SOLVER=tests/thrust-pcsat-wrapper COAR_IMAGE=coar:latest trait BoolLike { fn truthy(&self) -> bool; From cf01d7b548938cbcb16d3b49170568f581e2769b Mon Sep 17 00:00:00 2001 From: coeff-aij <175928954+coeff-aij@users.noreply.github.com> Date: Sun, 30 Aug 2026 15:53:31 +0900 Subject: [PATCH 102/142] Rename ResolvedCallable::Closure to Concrete --- src/analyze/basic_block.rs | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/src/analyze/basic_block.rs b/src/analyze/basic_block.rs index 7d31d68e..c4691466 100644 --- a/src/analyze/basic_block.rs +++ b/src/analyze/basic_block.rs @@ -132,7 +132,7 @@ impl PrecondCapture { } enum ResolvedCallable<'tcx> { - Closure(DefId, mir_ty::GenericArgsRef<'tcx>), + Concrete(DefId, mir_ty::GenericArgsRef<'tcx>), Generic(TypeParam), } @@ -918,7 +918,7 @@ impl<'tcx, 'ctx> Analyzer<'tcx, 'ctx> { // closure encoding that type_builder.build() cannot handle. let parent_count = self.tcx.generics_of(*closure_def_id).parent_count; let parent_args = self.tcx.mk_args(&closure_args[..parent_count]); - ResolvedCallable::Closure(*closure_def_id, parent_args) + ResolvedCallable::Concrete(*closure_def_id, parent_args) } mir_ty::TyKind::Param(ty) => ResolvedCallable::Generic(TypeParam::GenericType { param_def_id: self.type_builder.param_def_id(ty), @@ -933,9 +933,9 @@ impl<'tcx, 'ctx> Analyzer<'tcx, 'ctx> { let instance = mir_ty::Instance::try_resolve(self.tcx, typing_env, def_id, args).unwrap(); if let Some(instance) = instance { - ResolvedCallable::Closure(instance.def_id(), instance.args) + ResolvedCallable::Concrete(instance.def_id(), instance.args) } else { - ResolvedCallable::Closure(def_id, args) + ResolvedCallable::Concrete(def_id, args) } } } @@ -954,7 +954,7 @@ impl<'tcx, 'ctx> Analyzer<'tcx, 'ctx> { .expect("unknown closure type") .into() } - ResolvedCallable::Closure(resolved_def_id, resolved_args) => { + ResolvedCallable::Concrete(resolved_def_id, resolved_args) => { if let Some(def_ty) = self.ctx.def_ty_with_args(def_id, args, caller_def_id) { // otherwise nothing asks for a deferred impl method's type and its body goes unchecked if resolved_def_id != def_id { From bb83b7b272054165039e5b84e9ec3161925751a9 Mon Sep 17 00:00:00 2001 From: coeff-aij <175928954+coeff-aij@users.noreply.github.com> Date: Sun, 30 Aug 2026 16:13:31 +0900 Subject: [PATCH 103/142] test: add solver configuration environmental variables --- tests/ui/fail/trait_generic_impl.rs | 1 + tests/ui/pass/trait_generic_impl.rs | 1 + 2 files changed, 2 insertions(+) diff --git a/tests/ui/fail/trait_generic_impl.rs b/tests/ui/fail/trait_generic_impl.rs index 0f9430a8..592e1dde 100644 --- a/tests/ui/fail/trait_generic_impl.rs +++ b/tests/ui/fail/trait_generic_impl.rs @@ -1,5 +1,6 @@ //@error-in-other-file: Unsat //@compile-flags: -C debug-assertions=off +//@rustc-env: THRUST_SOLVER=tests/thrust-pcsat-wrapper COAR_IMAGE=coar:latest #[thrust_macros::context] trait Tr { diff --git a/tests/ui/pass/trait_generic_impl.rs b/tests/ui/pass/trait_generic_impl.rs index b8a4abee..967f8649 100644 --- a/tests/ui/pass/trait_generic_impl.rs +++ b/tests/ui/pass/trait_generic_impl.rs @@ -1,5 +1,6 @@ //@check-pass //@compile-flags: -C debug-assertions=off +//@rustc-env: THRUST_SOLVER=tests/thrust-pcsat-wrapper COAR_IMAGE=coar:latest #[thrust_macros::context] trait Tr { From b4834bf44dc1135502a16ba8df89a1c71f067b70 Mon Sep 17 00:00:00 2001 From: coeff-aij <175928954+coeff-aij@users.noreply.github.com> Date: Mon, 31 Aug 2026 14:22:36 +0900 Subject: [PATCH 104/142] Reject non-Fn predicates before building closure trait args --- src/refine/template.rs | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/src/refine/template.rs b/src/refine/template.rs index 6032e9c9..4c1e5d42 100644 --- a/src/refine/template.rs +++ b/src/refine/template.rs @@ -522,12 +522,14 @@ impl<'tcx> TypeBuilder<'tcx> { if trait_ref.self_ty() != param_ty.to_ty(self.tcx) { return None; } + // Reject non-`Fn`/`FnMut`/`FnOnce` predicates (e.g. `Sized`) before building any type, + // so that a `ParamTy` the current TypeBuilder cannot translate is never built here. + use mir_ty::ClosureKind::*; + let closure_kind = self.tcx.fn_trait_kind_from_def_id(trait_ref.def_id)?; tracing::debug!(?trait_ref.args); let receiver_type = self.build(trait_ref.args.type_at(0)); - - use mir_ty::ClosureKind::*; - let receiver_type = match self.tcx.fn_trait_kind_from_def_id(trait_ref.def_id)? { + let receiver_type = match closure_kind { Fn => rty::PointerType::immut_to(receiver_type).into(), FnMut => rty::PointerType::mut_to(receiver_type).into(), FnOnce => receiver_type, From ca531884573ed91ca74f87e9b0bd09cede478c8b Mon Sep 17 00:00:00 2001 From: coeff-aij <175928954+coeff-aij@users.noreply.github.com> Date: Mon, 31 Aug 2026 14:22:43 +0900 Subject: [PATCH 105/142] Use the method as caller_def_id for trait item types --- src/analyze/local_def.rs | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/src/analyze/local_def.rs b/src/analyze/local_def.rs index 7b4bfbec..117f42f8 100644 --- a/src/analyze/local_def.rs +++ b/src/analyze/local_def.rs @@ -295,9 +295,11 @@ impl<'tcx, 'ctx> Analyzer<'tcx, 'ctx> { .associated_item(self.local_def_id.to_def_id()) .trait_item_def_id .unwrap(); - let impl_did = self.tcx.parent(self.local_def_id.to_def_id()); - self.ctx - .def_ty_with_args(trait_item_did, trait_item_args, impl_did) + self.ctx.def_ty_with_args( + trait_item_did, + trait_item_args, + self.local_def_id.to_def_id(), + ) } // TODO: Remove this eager precompute together with From 6e3bf1b2f5315f4e9c6fa9447b186aa71e90f8bb Mon Sep 17 00:00:00 2001 From: coeff-aij <175928954+coeff-aij@users.noreply.github.com> Date: Mon, 31 Aug 2026 14:24:30 +0900 Subject: [PATCH 106/142] Carry enum type args in FlowBinding and drop type-param unification --- src/refine/env.rs | 16 ++--- src/rty.rs | 166 +--------------------------------------------- src/rty/params.rs | 54 +-------------- 3 files changed, 6 insertions(+), 230 deletions(-) diff --git a/src/refine/env.rs b/src/refine/env.rs index 88397fa6..a7072e5c 100644 --- a/src/refine/env.rs +++ b/src/refine/env.rs @@ -82,6 +82,7 @@ enum FlowBinding { discr: TempVarIdx, variants: IndexVec, sym: chc::DatatypeSymbol, + args: rty::RefinedTypeArgs, }, } @@ -822,6 +823,7 @@ where discr: discr_var, variants, sym: def.name.clone(), + args: ty.args.clone(), }; match var { Var::Local(local) => { @@ -968,6 +970,7 @@ where discr, variants, sym, + args, }) => { let field_tys: Vec<_> = variants .iter() @@ -975,18 +978,7 @@ where .map(|&v| self.var_type(v.into())) .collect(); - let arg_rtys = { - let def = self.enum_defs.enum_def(sym); - let expected_tys = def - .field_tys() - .map(|ty| rty::RefinedType::unrefined(ty.clone().vacuous()).boxed()); - let got_tys = field_tys.iter().map(|ty| ty.clone().into()); - rty::unify_tys_params(expected_tys, got_tys).into_args(def.ty_params, |_| { - panic!("var_type: should unify all params") - }) - }; - - let enum_ty = rty::EnumType::new(sym.clone(), arg_rtys); + let enum_ty = rty::EnumType::new(sym.clone(), args.clone()); let matcher_pred = chc::MatcherPred::new(sym.clone(), enum_ty.arg_sorts()).into(); PlaceType::enum_(enum_ty, matcher_pred, *discr, field_tys) } diff --git a/src/rty.rs b/src/rty.rs index a98652a5..c277c71f 100644 --- a/src/rty.rs +++ b/src/rty.rs @@ -24,7 +24,6 @@ //! //! - `subst_var`: Substitutes logical variables with logical terms. //! - `map_var`: Maps logical variables to other logical variables. -//! - `free_ty_params`: Collects free type parameters [`TypeParamIdx`] in the type. //! - `subst_ty_params`: Substitutes type parameters with other types. Since this replaces //! type parameters with refinement types, [`Type`] does not implement this, and //! [`RefinedType::subst_ty_params`] handles the substitution logic instead. @@ -37,7 +36,7 @@ //! - [`subtyping`]: Generates CHC constraints [`crate::chc`] from subtyping relations between types. //! - [`clause_builder`]: Helper to build [`crate::chc::Clause`] from the refinement types. -use std::collections::{HashMap, HashSet}; +use std::collections::HashMap; use pretty::{termcolor, Pretty}; use rustc_abi::VariantIdx; @@ -255,14 +254,6 @@ impl FunctionType { Type::Function(self) } - pub fn free_ty_params(&self) -> HashSet { - self.params - .iter() - .flat_map(RefinedType::free_ty_params) - .chain(self.ret.free_ty_params()) - .collect() - } - pub fn subst_ty_params(&mut self, subst: &TypeParamSubst) { let subst = subst.clone().vacuous(); for param in &mut self.params { @@ -271,15 +262,6 @@ impl FunctionType { self.ret.subst_ty_params(&subst); } - pub fn unify_ty_params(self, other: FunctionType) -> TypeParamSubst { - assert_eq!(self.params.len(), other.params.len()); - let mut tys1 = self.params; - tys1.push(*self.ret); - let mut tys2 = other.params; - tys2.push(*other.ret); - unify_tys_params(tys1, tys2) - } - /// Removes the parameter at the given index from this function type. /// /// References to the remaining parameters in other parameters' refinements and the @@ -562,24 +544,12 @@ impl PointerType { } } - pub fn free_ty_params(&self) -> HashSet { - self.elem.free_ty_params() - } - pub fn subst_ty_params(&mut self, subst: &TypeParamSubst) where T: chc::Var, { self.elem.subst_ty_params(subst) } - - pub fn unify_ty_params(self, other: PointerType) -> TypeParamSubst - where - T: chc::Var, - { - assert_eq!(self.kind, other.kind); - self.elem.unify_ty_params(*other.elem) - } } /// A tuple type. @@ -661,13 +631,6 @@ impl TupleType { } } - pub fn free_ty_params(&self) -> HashSet { - self.elems - .iter() - .flat_map(RefinedType::free_ty_params) - .collect() - } - pub fn subst_ty_params(&mut self, subst: &TypeParamSubst) where T: chc::Var, @@ -676,14 +639,6 @@ impl TupleType { elem.subst_ty_params(subst); } } - - pub fn unify_ty_params(self, other: TupleType) -> TypeParamSubst - where - T: chc::Var, - { - assert_eq!(self.elems.len(), other.elems.len()); - unify_tys_params(self.elems, other.elems) - } } /// A definition of an enum variant, found in [`EnumDatatypeDef`]. @@ -787,13 +742,6 @@ impl EnumType { } } - pub fn free_ty_params(&self) -> HashSet { - self.args - .iter() - .flat_map(RefinedType::free_ty_params) - .collect() - } - pub fn subst_ty_params(&mut self, subst: &TypeParamSubst) where T: chc::Var, @@ -802,14 +750,6 @@ impl EnumType { arg.subst_ty_params(subst); } } - - pub fn unify_ty_params(self, other: EnumType) -> TypeParamSubst - where - T: chc::Var, - { - assert_eq!(self.symbol, other.symbol); - unify_tys_params(self.args, other.args) - } } /// A type parameter. @@ -964,14 +904,6 @@ impl ArrayType { } } - pub fn free_ty_params(&self) -> HashSet { - self.index - .free_ty_params() - .into_iter() - .chain(self.elem.free_ty_params()) - .collect() - } - pub fn subst_ty_params(&mut self, subst: &TypeParamSubst) where T: chc::Var, @@ -979,13 +911,6 @@ impl ArrayType { self.index.subst_ty_params(subst); self.elem.subst_ty_params(subst); } - - pub fn unify_ty_params(self, other: ArrayType) -> TypeParamSubst - where - T: chc::Var, - { - unify_tys_params([*self.index, *self.elem], [*other.index, *other.elem]) - } } /// An underlying type of a refinement type. @@ -1278,23 +1203,6 @@ impl Type { Type::Enum(ty) => Type::Enum(ty.strip_refinement()), } } - - pub fn free_ty_params(&self) -> HashSet { - match self { - Type::Int | Type::Bool | Type::String | Type::Never => Default::default(), - Type::Param(ty) => std::iter::once(ty.type_param_index()).collect(), - Type::Alias(ty) => ty - .args() - .iter() - .flat_map(|ty| ty.free_ty_params()) - .collect(), - Type::Pointer(ty) => ty.free_ty_params(), - Type::Function(ty) => ty.free_ty_params(), - Type::Tuple(ty) => ty.free_ty_params(), - Type::Array(ty) => ty.free_ty_params(), - Type::Enum(ty) => ty.free_ty_params(), - } - } } impl Type { @@ -1896,10 +1804,6 @@ impl RefinedType { self.ty.strip_refinement() } - pub fn free_ty_params(&self) -> HashSet { - self.ty.free_ty_params() - } - pub fn subst_ty_params(&mut self, subst: &TypeParamSubst) where FV: chc::Var, @@ -1947,57 +1851,6 @@ impl RefinedType { { self.subst_ty_params(¶ms.into()); } - - pub fn unify_ty_params(self, other: RefinedType) -> TypeParamSubst - where - FV: chc::Var, - { - match (self.ty, other.ty) { - (Type::Int, Type::Int) - | (Type::Bool, Type::Bool) - | (Type::String, Type::String) - | (Type::Never, Type::Never) => Default::default(), - (Type::Param(pty), ty) if !ty.free_ty_params().contains(&pty.type_param_index()) => { - TypeParamSubst::singleton( - pty.type_param_index(), - RefinedType::new(ty.clone(), other.refinement.clone()), - ) - } - (ty, Type::Param(pty)) if !ty.free_ty_params().contains(&pty.type_param_index()) => { - TypeParamSubst::singleton( - pty.type_param_index(), - RefinedType::new(ty.clone(), self.refinement.clone()), - ) - } - (Type::Pointer(ty1), Type::Pointer(ty2)) => ty1.unify_ty_params(ty2), - (Type::Function(ty1), Type::Function(ty2)) => { - // TODO: what should we do for in-function refinement substs? - ty1.unify_ty_params(ty2).strip_refinement().vacuous() - } - (Type::Tuple(ty1), Type::Tuple(ty2)) => ty1.unify_ty_params(ty2), - (Type::Array(ty1), Type::Array(ty2)) => ty1.unify_ty_params(ty2), - (Type::Enum(ty1), Type::Enum(ty2)) => ty1.unify_ty_params(ty2), - (Type::Alias(a1), Type::Alias(a2)) - if a1.forall_sort_index() == a2.forall_sort_index() => - { - assert_eq!(a1.args().len(), a2.args().len()); - let args1: Vec> = a1 - .args() - .iter() - .cloned() - .map(|ty| RefinedType::unrefined(ty).vacuous()) - .collect(); - let args2: Vec> = a2 - .args() - .iter() - .cloned() - .map(|ty| RefinedType::unrefined(ty).vacuous()) - .collect(); - unify_tys_params(args1, args2) - } - (t1, t2) => panic!("unify_ty_params: mismatched types t1={:?}, t2={:?}", t1, t2), - } - } } impl RefinedType { @@ -2137,23 +1990,6 @@ fn subst_ty_params_in_term(term: &mut chc::Term, subst: &TypeParamSubst } } -pub fn unify_tys_params(tys1: I1, tys2: I2) -> TypeParamSubst -where - T: chc::Var, - I1: IntoIterator>, - I2: IntoIterator>, -{ - tys1.into_iter() - .zip(tys2) - .fold(Default::default(), |s1, (mut t1, mut t2)| { - t1.subst_ty_params(&s1); - t2.subst_ty_params(&s1); - let mut s2 = t1.unify_ty_params(t2); - s2.compose(s1); - s2 - }) -} - #[cfg(test)] mod tests { use super::{ParamType, TypeParamIdx}; diff --git a/src/rty/params.rs b/src/rty/params.rs index ef05138e..5ac87d18 100644 --- a/src/rty/params.rs +++ b/src/rty/params.rs @@ -5,9 +5,7 @@ use std::collections::BTreeMap; use pretty::{termcolor, Pretty}; use rustc_index::IndexVec; -use crate::chc; - -use super::{Closed, RefinedType, Type}; +use super::{Closed, RefinedType}; rustc_index::newtype_index! { /// An index representing a type parameter. @@ -54,7 +52,6 @@ impl TypeParamIdx { } pub type RefinedTypeArgs = IndexVec>; -pub type TypeArgs = IndexVec>; /// A substitution for type parameters that maps type parameters to refinement types. #[derive(Debug, Clone)] @@ -70,16 +67,6 @@ impl Default for TypeParamSubst { } } -impl From> for TypeParamSubst { - fn from(params: TypeArgs) -> Self { - let subst = params - .into_iter_enumerated() - .map(|(idx, ty)| (idx, RefinedType::unrefined(ty))) - .collect(); - Self { subst } - } -} - impl From> for TypeParamSubst { fn from(params: RefinedTypeArgs) -> Self { let subst = params.into_iter_enumerated().collect(); @@ -96,49 +83,10 @@ impl std::ops::Index for TypeParamSubst { } impl TypeParamSubst { - pub fn new(subst: BTreeMap>) -> Self { - Self { subst } - } - - pub fn singleton(idx: TypeParamIdx, ty: RefinedType) -> Self { - let mut subst = BTreeMap::default(); - subst.insert(idx, ty); - Self { subst } - } - pub fn get(&self, idx: TypeParamIdx) -> Option<&RefinedType> { self.subst.get(&idx) } - pub fn compose(&mut self, other: Self) - where - T: chc::Var, - { - for (idx, mut t1) in other.subst { - t1.subst_ty_params(self); - if let Some(t2) = self.subst.remove(&idx) { - t1.refinement.push_conj(t2.refinement); - } - self.subst.insert(idx, t1); - } - } - - pub fn into_args(mut self, expected_len: usize, mut default: F) -> RefinedTypeArgs - where - T: chc::Var, - F: FnMut(TypeParamIdx) -> RefinedType, - { - let mut args = RefinedTypeArgs::new(); - for idx in 0..expected_len { - let ty = self - .subst - .remove(&idx.into()) - .unwrap_or_else(|| default(idx.into())); - args.push(ty); - } - args - } - pub fn strip_refinement(self) -> TypeParamSubst { TypeParamSubst { subst: self From 454173ccd735c32e9767e9b3c1f7bc2a8104c6bf Mon Sep 17 00:00:00 2001 From: coeff-aij <175928954+coeff-aij@users.noreply.github.com> Date: Mon, 31 Aug 2026 14:24:34 +0900 Subject: [PATCH 107/142] Enable extended solver for trait_generic_method tests --- tests/ui/fail/trait_generic_method.rs | 1 + tests/ui/pass/trait_generic_method.rs | 1 + 2 files changed, 2 insertions(+) diff --git a/tests/ui/fail/trait_generic_method.rs b/tests/ui/fail/trait_generic_method.rs index fe65f7d1..f52dd8f1 100644 --- a/tests/ui/fail/trait_generic_method.rs +++ b/tests/ui/fail/trait_generic_method.rs @@ -1,5 +1,6 @@ //@error-in-other-file: Unsat //@compile-flags: -C debug-assertions=off +//@rustc-env: THRUST_SOLVER=tests/thrust-pcsat-wrapper COAR_IMAGE=coar:latest #[thrust_macros::context] trait Tr { diff --git a/tests/ui/pass/trait_generic_method.rs b/tests/ui/pass/trait_generic_method.rs index 0c181897..a9cd7c31 100644 --- a/tests/ui/pass/trait_generic_method.rs +++ b/tests/ui/pass/trait_generic_method.rs @@ -1,5 +1,6 @@ //@check-pass //@compile-flags: -C debug-assertions=off +//@rustc-env: THRUST_SOLVER=tests/thrust-pcsat-wrapper COAR_IMAGE=coar:latest #[thrust_macros::context] trait Tr { From fe1c2947e5a74c3bc5663460748d37cf30c949bc Mon Sep 17 00:00:00 2001 From: coeff-aij <175928954+coeff-aij@users.noreply.github.com> Date: Mon, 31 Aug 2026 14:39:49 +0900 Subject: [PATCH 108/142] test: add pass/fail regression tests for enum type args in FlowBinding --- tests/ui/fail/adt_generic_enum_mut.rs | 29 +++++++++++++++++++++++++ tests/ui/pass/adt_generic_enum_mut.rs | 31 +++++++++++++++++++++++++++ 2 files changed, 60 insertions(+) create mode 100644 tests/ui/fail/adt_generic_enum_mut.rs create mode 100644 tests/ui/pass/adt_generic_enum_mut.rs diff --git a/tests/ui/fail/adt_generic_enum_mut.rs b/tests/ui/fail/adt_generic_enum_mut.rs new file mode 100644 index 00000000..391c6795 --- /dev/null +++ b/tests/ui/fail/adt_generic_enum_mut.rs @@ -0,0 +1,29 @@ +//@error-in-other-file: Unsat +//@compile-flags: -C debug-assertions=off +//@rustc-env: THRUST_SOLVER=tests/thrust-pcsat-wrapper COAR_IMAGE=coar:latest + +// Same structure as the pass counterpart, except the assertion is false: the +// matched field has value `1`, so `assert!(v == 0)` must be rejected as +// unsatisfiable. Before the fix, the corrupted enum type reconstruction made +// the solver fail with `unification failure` instead of returning Unsat. + +enum Pair { + L(A), + R(B), +} + +struct Wrap { + p: Pair, +} + +#[thrust::callable] +fn check() { + let mut w: Wrap = Wrap { p: Pair::L(1u32) }; + let v = match &mut w.p { + Pair::L(x) => *x, + Pair::R(_) => unimplemented!(), + }; + assert!(v == 0); +} + +fn main() {} \ No newline at end of file diff --git a/tests/ui/pass/adt_generic_enum_mut.rs b/tests/ui/pass/adt_generic_enum_mut.rs new file mode 100644 index 00000000..02716285 --- /dev/null +++ b/tests/ui/pass/adt_generic_enum_mut.rs @@ -0,0 +1,31 @@ +//@check-pass +//@compile-flags: -C debug-assertions=off +//@rustc-env: THRUST_SOLVER=tests/thrust-pcsat-wrapper COAR_IMAGE=coar:latest + +// The generic parameter `T` shares the local index `T0` with the enum's own +// type parameter `A`. Reconstructing the enum type from the matched fields used +// to conflate the two, emitting a corrupted `Pair` datatype instance +// next to the correct `Pair` (solver: `unification failure`). The enum +// type args are now carried by the flow binding, so the assertion below is +// verified. + +enum Pair { + L(A), + R(B), +} + +struct Wrap { + p: Pair, +} + +#[thrust::callable] +fn check() { + let mut w: Wrap = Wrap { p: Pair::L(1u32) }; + let v = match &mut w.p { + Pair::L(x) => *x, + Pair::R(_) => unimplemented!(), + }; + assert!(v == 1); +} + +fn main() {} \ No newline at end of file From 5ce937e9f7cd2f9dd03103fa1679c910ebdda70a Mon Sep 17 00:00:00 2001 From: coeff-aij <175928954+coeff-aij@users.noreply.github.com> Date: Tue, 1 Sep 2026 14:09:14 +0900 Subject: [PATCH 109/142] add: debug info for (declare-forall-sort) in .smt2 file --- src/chc.rs | 38 ++++++++++++++++++++++++++++++++++---- src/chc/smtlib2.rs | 11 +++++++---- src/refine/template.rs | 20 +++++++++++--------- 3 files changed, 52 insertions(+), 17 deletions(-) diff --git a/src/chc.rs b/src/chc.rs index 025b1e73..a1bf6441 100644 --- a/src/chc.rs +++ b/src/chc.rs @@ -126,6 +126,23 @@ impl ForallSortIdx { } } +/// A forall sort declaration, carrying debug information about the type +/// parameter it was issued for. +/// +/// [`System`] contains `Vec` that manages the indices and the debug information +/// of the sort-level variables. +#[derive(Debug, Clone)] +pub struct ForallSortDef { + pub idx: ForallSortIdx, + pub debug_info: DebugInfo, +} + +impl ForallSortDef { + pub fn new(idx: ForallSortIdx, debug_info: DebugInfo) -> Self { + Self { idx, debug_info } + } +} + /// A sort is the type of a logical term. #[derive(Debug, Clone, PartialEq, Eq, Hash, PartialOrd, Ord)] pub enum Sort { @@ -2191,7 +2208,7 @@ pub struct System { pub user_defined_pred_defs: Vec, pub clauses: IndexVec, pub pred_vars: IndexVec, - pub forall_sorts: Vec, + pub forall_sorts: Vec, pub num_forall_sort_idx: ForallSortIdx, /// Reverse map from [`ForallSortIdx`] to the local index of the type /// parameter it was issued for, populated by the analyzer. Used during @@ -2214,10 +2231,11 @@ impl System { self.forall_pred_vars.insert(pred); } - pub fn new_forall_sort(&mut self) -> ForallSortIdx { + pub fn new_forall_sort(&mut self, debug_info: DebugInfo) -> ForallSortIdx { let new_idx = self.num_forall_sort_idx; self.num_forall_sort_idx += 1; - self.forall_sorts.push(new_idx); + self.forall_sorts + .push(ForallSortDef::new(new_idx, debug_info)); new_idx } @@ -2411,7 +2429,7 @@ mod tests { #[test] fn declares_forall_default_once() { let mut system = System::default(); - let idx = system.new_forall_sort(); + let idx = system.new_forall_sort(DebugInfo::default()); let default = Term::default_for(&Sort::forall(idx)); let body = Atom::new( Pred::Known(KnownPred::EQUAL), @@ -2428,4 +2446,16 @@ mod tests { assert_eq!(smt.matches("(declare-const default_a0 a0)").count(), 1); assert_eq!(smt.matches("default_a0").count(), 2); } + + #[test] + fn emits_forall_sort_debug_info() { + let mut system = System::default(); + system.new_forall_sort( + DebugInfo::default().with_context("type_param", "ParamTy T/#0 (decl=DefId(...))"), + ); + + let smt = system.smtlib2().to_string(); + assert!(smt.contains("; forall sort a0: type_param=ParamTy T/#0 (decl=DefId(...))")); + assert!(smt.contains("(declare-forall-sort a0)")); + } } diff --git a/src/chc/smtlib2.rs b/src/chc/smtlib2.rs index 63b2e954..e100f326 100644 --- a/src/chc/smtlib2.rs +++ b/src/chc/smtlib2.rs @@ -698,14 +698,17 @@ impl<'a> std::fmt::Display for System<'a> { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { writeln!(f, "(set-logic HORN)\n")?; - for forall_sort_idx in &self.inner.forall_sorts { - writeln!(f, "(declare-forall-sort {})\n", forall_sort_idx)?; + for forall_sort_def in &self.inner.forall_sorts { + if !forall_sort_def.debug_info.is_empty() { + writeln!(f, "{}", forall_sort_def.debug_info.display("; "))?; + } + writeln!(f, "(declare-forall-sort {})\n", forall_sort_def.idx)?; } - for forall_sort_idx in &self.inner.forall_sorts { + for forall_sort_def in &self.inner.forall_sorts { writeln!( f, "(declare-const default_{} {})\n", - forall_sort_idx, forall_sort_idx + forall_sort_def.idx, forall_sort_def.idx )?; } diff --git a/src/refine/template.rs b/src/refine/template.rs index 4c1e5d42..c75fce3f 100644 --- a/src/refine/template.rs +++ b/src/refine/template.rs @@ -180,13 +180,10 @@ impl<'tcx> TypeBuilder<'tcx> { local_idx: param_local_idx, }) .or_insert_with(|| { - let idx = self.system.borrow_mut().new_forall_sort(); - tracing::debug!( - "issue the new ForallSortIdx {} for ParamTy {:?} (decl={:?}).", - idx, - ty, - param_def_id - ); + let desc = format!("ParamTy {:?} (decl={:?})", ty, param_def_id); + let debug_info = chc::DebugInfo::default().with_context("type_param", desc.clone()); + let idx = self.system.borrow_mut().new_forall_sort(debug_info); + tracing::debug!("issue the new ForallSortIdx {} for {desc}.", idx); idx }); rty::ParamType::new(rty::TypeParamIdx::from(param_local_idx), *forall_sort_idx).into() @@ -214,8 +211,13 @@ impl<'tcx> TypeBuilder<'tcx> { let index = type_params .entry(TypeParam::AssocType(ty.def_id, args.clone())) .or_insert_with(|| { - let idx = self.system.borrow_mut().new_forall_sort(); - tracing::debug!("issue the new ForallSortIdx {} for AliasTy {:?} with (def_id = {:?}, args = {:?}).", idx, ty, ty.def_id, args); + let desc = format!( + "AliasTy {:?} with (def_id = {:?}, args = {:?})", + ty, ty.def_id, args + ); + let debug_info = chc::DebugInfo::default().with_context("type_param", desc.clone()); + let idx = self.system.borrow_mut().new_forall_sort(debug_info); + tracing::debug!("issue the new ForallSortIdx {} for {desc}.", idx); idx }); From cdda44425eb73d03cac820fcb02fb9ba84b1fc5c Mon Sep 17 00:00:00 2001 From: coeff-aij <175928954+coeff-aij@users.noreply.github.com> Date: Tue, 1 Sep 2026 18:33:21 +0900 Subject: [PATCH 110/142] fix: place (declare-forall-fun) after (declare-datatypes) in .smt2 file --- src/chc/smtlib2.rs | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/src/chc/smtlib2.rs b/src/chc/smtlib2.rs index e100f326..53289ed9 100644 --- a/src/chc/smtlib2.rs +++ b/src/chc/smtlib2.rs @@ -712,10 +712,6 @@ impl<'a> std::fmt::Display for System<'a> { )?; } - for pred in &self.inner.forall_pred_vars { - writeln!(f, "{}\n", ForallPredDef::new(&self.ctx, pred))?; - } - writeln!(f, "{}\n", Datatypes::new(&self.ctx, self.ctx.datatypes()))?; for datatype in self.ctx.datatypes() { writeln!(f, "{}", DatatypeDiscrFun::new(&self.ctx, datatype))?; @@ -745,6 +741,11 @@ impl<'a> std::fmt::Display for System<'a> { (select ({array} t) (- ({len} t) 1)))))\n", )?; } + writeln!(f)?; + + for pred in &self.inner.forall_pred_vars { + writeln!(f, "{}\n", ForallPredDef::new(&self.ctx, pred))?; + } // insert command from #![thrust::raw_command()] here for raw_command in &self.inner.raw_commands { From a560cd90ef9f8a3c82dd203838b6c4ab5169adbe Mon Sep 17 00:00:00 2001 From: coeff-aij <175928954+coeff-aij@users.noreply.github.com> Date: Wed, 2 Sep 2026 01:11:18 +0900 Subject: [PATCH 111/142] fix: emit declare-const for forall sort default only when used --- src/chc.rs | 77 ++++++++++++++++++++++++++++++++++++++++++++++ src/chc/smtlib2.rs | 4 +++ 2 files changed, 81 insertions(+) diff --git a/src/chc.rs b/src/chc.rs index a1bf6441..8aa0b7c3 100644 --- a/src/chc.rs +++ b/src/chc.rs @@ -2295,6 +2295,25 @@ impl System { } } + /// The set of forall sorts whose default value is referenced (via + /// [`Term::ForallDefault`]) in some clause. Only these need a + /// `declare-const default_` definition in the SMT-LIB2 output. + pub fn used_forall_default_sorts(&self) -> HashSet { + let mut used = HashSet::new(); + for clause in &self.clauses { + for atom in clause + .body + .iter_atoms() + .chain(std::iter::once(&clause.head)) + { + for arg in &atom.args { + collect_forall_defaults(arg, &mut used); + } + } + } + used + } + pub fn push_clause(&mut self, clause: Clause) -> Option { if clause.is_nop() { return None; @@ -2422,6 +2441,52 @@ impl System { } } +/// Collects the forall sorts referenced via [`Term::ForallDefault`] in `term` +/// into `used`. +fn collect_forall_defaults(term: &Term, used: &mut HashSet) { + match term { + Term::ForallDefault(idx) => { + used.insert(*idx); + } + Term::Box(t) | Term::BoxCurrent(t) | Term::MutCurrent(t) | Term::MutFinal(t) => { + collect_forall_defaults(t, used) + } + Term::Mut(t1, t2) => { + collect_forall_defaults(t1, used); + collect_forall_defaults(t2, used); + } + Term::App(_, args) => { + for t in args { + collect_forall_defaults(t, used); + } + } + Term::SeqConcat(_, t) => { + for arg in t.iter_args() { + collect_forall_defaults(arg, used); + } + } + Term::Tuple(ts) => { + for t in ts { + collect_forall_defaults(t, used); + } + } + Term::TupleProj(t, _) => collect_forall_defaults(t, used), + Term::DatatypeCtor(_, _, args) => { + for t in args { + collect_forall_defaults(t, used); + } + } + Term::DatatypeDiscr(_, t) => collect_forall_defaults(t, used), + Term::Null + | Term::Var(_) + | Term::Bool(_) + | Term::Int(_) + | Term::String(_) + | Term::ArrayEmpty(_, _) + | Term::FormulaQuantifiedVar(_, _) => {} + } +} + #[cfg(test)] mod tests { use super::*; @@ -2447,6 +2512,18 @@ mod tests { assert_eq!(smt.matches("default_a0").count(), 2); } + #[test] + fn does_not_declare_default_for_unused_forall_sort() { + let mut system = System::default(); + system.new_forall_sort(DebugInfo::default()); + system.new_forall_sort(DebugInfo::default()); + + let smt = system.smtlib2().to_string(); + assert_eq!(smt.matches("(declare-const default_").count(), 0); + assert_eq!(smt.matches("(declare-forall-sort a0)").count(), 1); + assert_eq!(smt.matches("(declare-forall-sort a1)").count(), 1); + } + #[test] fn emits_forall_sort_debug_info() { let mut system = System::default(); diff --git a/src/chc/smtlib2.rs b/src/chc/smtlib2.rs index 53289ed9..790a0661 100644 --- a/src/chc/smtlib2.rs +++ b/src/chc/smtlib2.rs @@ -704,7 +704,11 @@ impl<'a> std::fmt::Display for System<'a> { } writeln!(f, "(declare-forall-sort {})\n", forall_sort_def.idx)?; } + let used_forall_defaults = self.inner.used_forall_default_sorts(); for forall_sort_def in &self.inner.forall_sorts { + if !used_forall_defaults.contains(&forall_sort_def.idx) { + continue; + } writeln!( f, "(declare-const default_{} {})\n", From 8816d94d23b3a054d5849cd246b77a4b80559e47 Mon Sep 17 00:00:00 2001 From: coeff-aij <175928954+coeff-aij@users.noreply.github.com> Date: Wed, 2 Sep 2026 01:14:43 +0900 Subject: [PATCH 112/142] test: fix expected debug info format in emits_forall_sort_debug_info --- src/chc.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/chc.rs b/src/chc.rs index 8aa0b7c3..aba859eb 100644 --- a/src/chc.rs +++ b/src/chc.rs @@ -2532,7 +2532,7 @@ mod tests { ); let smt = system.smtlib2().to_string(); - assert!(smt.contains("; forall sort a0: type_param=ParamTy T/#0 (decl=DefId(...))")); + assert!(smt.contains("; type_param=ParamTy T/#0 (decl=DefId(...))")); assert!(smt.contains("(declare-forall-sort a0)")); } } From 66e23465ff1685a462ab5d662df945d092e83a45 Mon Sep 17 00:00:00 2001 From: coeff-aij <175928954+coeff-aij@users.noreply.github.com> Date: Wed, 2 Sep 2026 01:27:49 +0900 Subject: [PATCH 113/142] add: verification examples for bank account operations --- tests/ui/fail/traits/try_withdraw.rs | 36 ++++++++++++++++++++++++ tests/ui/fail/traits/withdraw_deposit.rs | 33 ++++++++++++++++++++++ tests/ui/pass/traits/try_withdraw.rs | 36 ++++++++++++++++++++++++ tests/ui/pass/traits/withdraw_deposit.rs | 33 ++++++++++++++++++++++ 4 files changed, 138 insertions(+) create mode 100644 tests/ui/fail/traits/try_withdraw.rs create mode 100644 tests/ui/fail/traits/withdraw_deposit.rs create mode 100644 tests/ui/pass/traits/try_withdraw.rs create mode 100644 tests/ui/pass/traits/withdraw_deposit.rs diff --git a/tests/ui/fail/traits/try_withdraw.rs b/tests/ui/fail/traits/try_withdraw.rs new file mode 100644 index 00000000..75bdc8f1 --- /dev/null +++ b/tests/ui/fail/traits/try_withdraw.rs @@ -0,0 +1,36 @@ +//@error-in-other-file: Unsat +//@compile-flags: -C debug-assertions=off +//@rustc-env: THRUST_SOLVER=tests/thrust-pcsat-wrapper COAR_IMAGE=coar:latest +extern crate thrust_macros; +use thrust_macros::{context, requires, ensures, predicate}; +use thrust_models::{exists, forall, Model}; + +#[context] +trait Account { + #[ensures(Self::is_balance(*self, result))] + fn balance(&self) -> u32; + #[requires(exists(|x| Self::is_balance(*self, x)))] + #[ensures(forall(|x| Self::is_balance(*self, x) ==> Self::is_balance(!self, x + amount)))] + fn deposit(&mut self, amount: u32); + #[requires(exists(|x| Self::is_balance(*self, x) && x >= amount))] + #[ensures(forall(|x| Self::is_balance(*self, x) ==> Self::is_balance(!self, x - amount)))] + fn withdraw(&mut self, amount: u32); + #[predicate] + fn is_balance(self, balance: u32) -> bool; +} + +#[requires(exists(|from| A::is_balance(*a, from)))] +#[ensures(forall(|from| A::is_balance(*a, from) ==> exists(|to| A::is_balance(!a, to) && to + result == from)))] +fn try_withdraw(a: &mut A, amount: u32) -> u32 +where + A: Model, + ::Ty: Model +{ + // if a.balance() < amount { + // return 0; + // } + a.withdraw(amount); + return amount; +} + +fn main() {} diff --git a/tests/ui/fail/traits/withdraw_deposit.rs b/tests/ui/fail/traits/withdraw_deposit.rs new file mode 100644 index 00000000..2c01c720 --- /dev/null +++ b/tests/ui/fail/traits/withdraw_deposit.rs @@ -0,0 +1,33 @@ +//@error-in-other-file: Unsat +//@compile-flags: -C debug-assertions=off +//@rustc-env: THRUST_SOLVER=tests/thrust-pcsat-wrapper COAR_IMAGE=coar:latest +extern crate thrust_macros; +use thrust_macros::{context, requires, ensures, predicate}; +use thrust_models::{exists, forall, Model}; + +#[context] +trait Account { + #[ensures(Self::is_balance(*self, result))] + fn balance(&self) -> u32; + #[requires(exists(|x| Self::is_balance(*self, x)))] + #[ensures(forall(|x| Self::is_balance(*self, x) ==> Self::is_balance(!self, x + amount)))] + fn deposit(&mut self, amount: u32); + #[requires(exists(|x| Self::is_balance(*self, x) && x >= amount))] + #[ensures(forall(|x| Self::is_balance(*self, x) ==> Self::is_balance(!self, x - amount)))] + fn withdraw(&mut self, amount: u32); + #[predicate] + fn is_balance(self, balance: u32) -> bool; +} + +#[requires(exists(|from| A::is_balance(*a, from) && from >= 10))] +#[ensures(forall(|from| A::is_balance(*a, from) ==> exists(|to| A::is_balance(!a, to) && to == from)))] +fn withdraw_deposit(a: &mut A) +where + A: Model, + ::Ty: Model +{ + a.withdraw(20); + a.deposit(10); +} + +fn main() {} diff --git a/tests/ui/pass/traits/try_withdraw.rs b/tests/ui/pass/traits/try_withdraw.rs new file mode 100644 index 00000000..052638f5 --- /dev/null +++ b/tests/ui/pass/traits/try_withdraw.rs @@ -0,0 +1,36 @@ +//@check-pass +//@compile-flags: -C debug-assertions=off +//@rustc-env: THRUST_SOLVER=tests/thrust-pcsat-wrapper COAR_IMAGE=coar:latest +extern crate thrust_macros; +use thrust_macros::{context, requires, ensures, predicate}; +use thrust_models::{exists, forall, Model}; + +#[context] +trait Account { + #[ensures(Self::is_balance(*self, result))] + fn balance(&self) -> u32; + #[requires(exists(|x| Self::is_balance(*self, x)))] + #[ensures(forall(|x| Self::is_balance(*self, x) ==> Self::is_balance(!self, x + amount)))] + fn deposit(&mut self, amount: u32); + #[requires(exists(|x| Self::is_balance(*self, x) && x >= amount))] + #[ensures(forall(|x| Self::is_balance(*self, x) ==> Self::is_balance(!self, x - amount)))] + fn withdraw(&mut self, amount: u32); + #[predicate] + fn is_balance(self, balance: u32) -> bool; +} + +#[requires(exists(|from| A::is_balance(*a, from)))] +#[ensures(forall(|from| A::is_balance(*a, from) ==> exists(|to| A::is_balance(!a, to) && to + result == from)))] +fn try_withdraw(a: &mut A, amount: u32) -> u32 +where + A: Model, + ::Ty: Model +{ + if a.balance() < amount { + return 0; + } + a.withdraw(amount); + return amount; +} + +fn main() {} diff --git a/tests/ui/pass/traits/withdraw_deposit.rs b/tests/ui/pass/traits/withdraw_deposit.rs new file mode 100644 index 00000000..b7332938 --- /dev/null +++ b/tests/ui/pass/traits/withdraw_deposit.rs @@ -0,0 +1,33 @@ +//@check-pass +//@compile-flags: -C debug-assertions=off +//@rustc-env: THRUST_SOLVER=tests/thrust-pcsat-wrapper COAR_IMAGE=coar:latest +extern crate thrust_macros; +use thrust_macros::{context, requires, ensures, predicate}; +use thrust_models::{exists, forall, Model}; + +#[context] +trait Account { + #[ensures(Self::is_balance(*self, result))] + fn balance(&self) -> u32; + #[requires(exists(|x| Self::is_balance(*self, x)))] + #[ensures(forall(|x| Self::is_balance(*self, x) ==> Self::is_balance(!self, x + amount)))] + fn deposit(&mut self, amount: u32); + #[requires(exists(|x| Self::is_balance(*self, x) && x >= amount))] + #[ensures(forall(|x| Self::is_balance(*self, x) ==> Self::is_balance(!self, x - amount)))] + fn withdraw(&mut self, amount: u32); + #[predicate] + fn is_balance(self, balance: u32) -> bool; +} + +#[requires(exists(|from| A::is_balance(*a, from) && from >= 10))] +#[ensures(forall(|from| A::is_balance(*a, from) ==> exists(|to| A::is_balance(!a, to) && to == from)))] +fn withdraw_deposit(a: &mut A) +where + A: Model, + ::Ty: Model +{ + a.withdraw(10); + a.deposit(10); +} + +fn main() {} From 5094a691c0b27b83ad6cfb39b32bb654ad14f574 Mon Sep 17 00:00:00 2001 From: coeff-aij <175928954+coeff-aij@users.noreply.github.com> Date: Wed, 2 Sep 2026 13:00:32 +0900 Subject: [PATCH 114/142] add: test for impl and generic function using trait --- tests/ui/pass/traits/generic_impl.rs | 45 ++++++++++++++++++++++++++++ 1 file changed, 45 insertions(+) create mode 100644 tests/ui/pass/traits/generic_impl.rs diff --git a/tests/ui/pass/traits/generic_impl.rs b/tests/ui/pass/traits/generic_impl.rs new file mode 100644 index 00000000..ba5fc012 --- /dev/null +++ b/tests/ui/pass/traits/generic_impl.rs @@ -0,0 +1,45 @@ +//@rustc-env: THRUST_SOLVER=tests/thrust-pcsat-wrapper COAR_IMAGE=coar:latest +//@check-pass + +use thrust_models::Model; + +#[thrust_macros::context] +trait Foo { + type Item; + + #[thrust_macros::predicate] + fn valid(self, x: Self::Item) -> bool; +} + +struct Bar(T); + +#[thrust_macros::context] +impl Foo for Bar +where + T: Foo + Model, + ::Item: Model, + ::Ty: PartialEq, +{ + type Item = T::Item; + + #[thrust_macros::predicate] + fn valid(self, x: Self::Item) -> bool { + "true"; true + } +} + +impl Model for Bar { + type Ty = Bar; +} + +#[thrust_macros::requires(T::valid(x, v))] +#[thrust_macros::ensures(T::valid(result, v))] +fn identity(x: T, v: T::Item) -> T +where + T: Foo + Model, + ::Item: Model, +{ + x +} + +fn main() {} \ No newline at end of file From 50f63ef97e9ea480797a1422df25d9866449e38b Mon Sep 17 00:00:00 2001 From: coeff-aij <175928954+coeff-aij@users.noreply.github.com> Date: Sun, 6 Sep 2026 02:30:16 +0900 Subject: [PATCH 115/142] Replace invariant_context with context in annot_simple_loop_self.rs `#[thrust_macros::invariant_context]` was folded into `#[thrust_macros::context]` (36f8ffe), so the test no longer compiled (E0433, then E0401 on the generic parameter in `invariant!`). With `#[thrust_macros::context]` on the generic function the test verifies again. Co-Authored-By: Claude Fable 5.1 --- tests/ui/pass/traits/annot_simple_loop_self.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/ui/pass/traits/annot_simple_loop_self.rs b/tests/ui/pass/traits/annot_simple_loop_self.rs index c1803b05..1e30181f 100644 --- a/tests/ui/pass/traits/annot_simple_loop_self.rs +++ b/tests/ui/pass/traits/annot_simple_loop_self.rs @@ -12,7 +12,7 @@ trait A { fn p(self, x: i64) -> bool; } -#[thrust_macros::invariant_context] +#[thrust_macros::context] #[thrust_macros::requires(T::p(*a, x))] #[thrust_macros::ensures(T::p(*a, result))] fn target(a: &T, x: i64) -> i64 { From fb6dff0983a301beebd2583b783b58e97e59698f Mon Sep 17 00:00:00 2001 From: coeff-aij <175928954+coeff-aij@users.noreply.github.com> Date: Sun, 6 Sep 2026 02:30:22 +0900 Subject: [PATCH 116/142] Add fail counterparts for the pass/traits tests Each fail file is its pass twin with one narrow break, checked to yield `verification error: Unsat` with the pinned pcsat configuration: - simple_loop, simple_loop_2int, loop_unbound: drop the `T::p(x)` precondition, so the first `a.f(v)` call has an unimplied precondition. - simple_loop_call: the concrete `impl A for B` returns `x - 1`, violating the inherited `ensures p(result)` with `p(x) = x > 0`. - generic_impl: drop `requires T::valid(x, v)`; the impl predicate is `"true"`, so this is the only observable break. - option_map: return `None` in the `Some` arm. - annot_simple_loop_self: `v = a.f(v) + 1` in the loop body, refuted through the explicit invariant. No fail twin for simple_loop_self: with the trait predicate taking a `Self` argument (`q_p`), pcsat times out (60s and 180s) on every break tried, including the `requires(true)` weakening that is refuted in 0.3s for simple_loop. Body-level breaks on the loop tests (`v = a.f(v) + 1`, `let mut v = 0`, returning `0`) also time out unless the loop invariant is explicit; the refutation needs a non-trivial instance of the forall predicate or a loop unrolling that the solver does not find. Co-Authored-By: Claude Fable 5.1 --- .../ui/fail/traits/annot_simple_loop_self.rs | 30 ++++++++++++ tests/ui/fail/traits/generic_impl.rs | 44 +++++++++++++++++ tests/ui/fail/traits/loop_unbound.rs | 28 +++++++++++ tests/ui/fail/traits/option_map.rs | 25 ++++++++++ tests/ui/fail/traits/simple_loop.rs | 28 +++++++++++ tests/ui/fail/traits/simple_loop_2int.rs | 28 +++++++++++ tests/ui/fail/traits/simple_loop_call.rs | 49 +++++++++++++++++++ 7 files changed, 232 insertions(+) create mode 100644 tests/ui/fail/traits/annot_simple_loop_self.rs create mode 100644 tests/ui/fail/traits/generic_impl.rs create mode 100644 tests/ui/fail/traits/loop_unbound.rs create mode 100644 tests/ui/fail/traits/option_map.rs create mode 100644 tests/ui/fail/traits/simple_loop.rs create mode 100644 tests/ui/fail/traits/simple_loop_2int.rs create mode 100644 tests/ui/fail/traits/simple_loop_call.rs diff --git a/tests/ui/fail/traits/annot_simple_loop_self.rs b/tests/ui/fail/traits/annot_simple_loop_self.rs new file mode 100644 index 00000000..a5e91879 --- /dev/null +++ b/tests/ui/fail/traits/annot_simple_loop_self.rs @@ -0,0 +1,30 @@ +//@error-in-other-file: Unsat +//@compile-flags: -C debug-assertions=off +//@rustc-env: THRUST_SOLVER=tests/thrust-pcsat-wrapper THRUST_SOLVER_TIMEOUT_SECS=60 COAR_IMAGE=coar:latest + +#[thrust_macros::context] +trait A { + #[thrust_macros::requires(Self::p(*self, x))] + #[thrust_macros::ensures(Self::p(*self, result))] + fn f(&self, x: i64) -> i64; + + #[thrust_macros::predicate] + fn p(self, x: i64) -> bool; +} + +#[thrust_macros::context] +#[thrust_macros::requires(T::p(*a, x))] +#[thrust_macros::ensures(T::p(*a, result))] +fn target(a: &T, x: i64) -> i64 { + let mut v = x; + let mut i = 0; + while i < 3 { + thrust_macros::invariant!(|a: &T, v: i64| T::p(*a, v)); + v = a.f(v) + 1; + i += 1; + } + + v +} + +fn main() {} diff --git a/tests/ui/fail/traits/generic_impl.rs b/tests/ui/fail/traits/generic_impl.rs new file mode 100644 index 00000000..a834d5e3 --- /dev/null +++ b/tests/ui/fail/traits/generic_impl.rs @@ -0,0 +1,44 @@ +//@rustc-env: THRUST_SOLVER=tests/thrust-pcsat-wrapper COAR_IMAGE=coar:latest +//@error-in-other-file: Unsat + +use thrust_models::Model; + +#[thrust_macros::context] +trait Foo { + type Item; + + #[thrust_macros::predicate] + fn valid(self, x: Self::Item) -> bool; +} + +struct Bar(T); + +#[thrust_macros::context] +impl Foo for Bar +where + T: Foo + Model, + ::Item: Model, + ::Ty: PartialEq, +{ + type Item = T::Item; + + #[thrust_macros::predicate] + fn valid(self, x: Self::Item) -> bool { + "true"; true + } +} + +impl Model for Bar { + type Ty = Bar; +} + +#[thrust_macros::ensures(T::valid(result, v))] +fn identity(x: T, v: T::Item) -> T +where + T: Foo + Model, + ::Item: Model, +{ + x +} + +fn main() {} \ No newline at end of file diff --git a/tests/ui/fail/traits/loop_unbound.rs b/tests/ui/fail/traits/loop_unbound.rs new file mode 100644 index 00000000..54bfe523 --- /dev/null +++ b/tests/ui/fail/traits/loop_unbound.rs @@ -0,0 +1,28 @@ +//@error-in-other-file: Unsat +//@compile-flags: -C debug-assertions=off +//@rustc-env: THRUST_SOLVER=tests/thrust-pcsat-wrapper THRUST_SOLVER_TIMEOUT_SECS=60 COAR_IMAGE=coar:latest + +#[thrust_macros::context] +trait A { + #[thrust_macros::requires(Self::p(x))] + #[thrust_macros::ensures(Self::p(result))] + fn f(&self, x: i64) -> i64; + + #[thrust_macros::predicate] + fn p(x: i64) -> bool; +} + +#[thrust_macros::requires(true)] +#[thrust_macros::ensures(T::p(result))] +fn target(a: &T, x: i64, n: u64) -> i64 { + let mut v = x; + let mut i = 0; + while i < n { + v = a.f(v); + i += 1; + } + + v +} + +fn main() {} diff --git a/tests/ui/fail/traits/option_map.rs b/tests/ui/fail/traits/option_map.rs new file mode 100644 index 00000000..262fba38 --- /dev/null +++ b/tests/ui/fail/traits/option_map.rs @@ -0,0 +1,25 @@ +//@error-in-other-file: Unsat +//@compile-flags: -C debug-assertions=off +//@rustc-env: THRUST_SOLVER=tests/thrust-pcsat-wrapper THRUST_SOLVER_TIMEOUT_SECS=60 COAR_IMAGE=coar:latest + +#[thrust_macros::requires( + opt == None || thrust_models::exists(|i| opt == Some(i) && thrust_macros::pre!(f(i))) +)] +#[thrust_macros::ensures( + (opt == None && result == None) + || thrust_models::exists(|i| thrust_models::exists(|j| + opt == Some(i) && thrust_macros::post!(f(i), j) && result == Some(j))) +)] +fn map(opt: Option, f: F) -> Option +where + T: thrust_models::Model, T::Ty: PartialEq, + U: thrust_models::Model, U::Ty: PartialEq, + F: FnOnce(T) -> U, +{ + match opt { + Some(i) => None, + None => None, + } +} + +fn main() {} diff --git a/tests/ui/fail/traits/simple_loop.rs b/tests/ui/fail/traits/simple_loop.rs new file mode 100644 index 00000000..543ea36c --- /dev/null +++ b/tests/ui/fail/traits/simple_loop.rs @@ -0,0 +1,28 @@ +//@error-in-other-file: Unsat +//@compile-flags: -C debug-assertions=off +//@rustc-env: THRUST_SOLVER=tests/thrust-pcsat-wrapper THRUST_SOLVER_TIMEOUT_SECS=60 COAR_IMAGE=coar:latest + +#[thrust_macros::context] +trait A { + #[thrust_macros::requires(Self::p(x))] + #[thrust_macros::ensures(Self::p(result))] + fn f(&self, x: i64) -> i64; + + #[thrust_macros::predicate] + fn p(x: i64) -> bool; +} + +#[thrust_macros::requires(true)] +#[thrust_macros::ensures(T::p(result))] +fn target(a: &T, x: i64) -> i64 { + let mut v = x; + let mut i = 0; + while i < 3 { + v = a.f(v); + i += 1; + } + + v +} + +fn main() {} diff --git a/tests/ui/fail/traits/simple_loop_2int.rs b/tests/ui/fail/traits/simple_loop_2int.rs new file mode 100644 index 00000000..883716c0 --- /dev/null +++ b/tests/ui/fail/traits/simple_loop_2int.rs @@ -0,0 +1,28 @@ +//@error-in-other-file: Unsat +//@compile-flags: -C debug-assertions=off +//@rustc-env: THRUST_SOLVER=tests/thrust-pcsat-wrapper THRUST_SOLVER_TIMEOUT_SECS=60 COAR_IMAGE=coar:latest + +#[thrust_macros::context] +trait A { + #[thrust_macros::requires(Self::p(x, x))] + #[thrust_macros::ensures(Self::p(result, result))] + fn f(&self, x: i64) -> i64; + + #[thrust_macros::predicate] + fn p(x: i64, y: i64) -> bool; +} + +#[thrust_macros::requires(true)] +#[thrust_macros::ensures(T::p(result, result))] +fn target(a: &T, x: i64) -> i64 { + let mut v = x; + let mut i = 0; + while i < 3 { + v = a.f(v); + i += 1; + } + + v +} + +fn main() {} diff --git a/tests/ui/fail/traits/simple_loop_call.rs b/tests/ui/fail/traits/simple_loop_call.rs new file mode 100644 index 00000000..1c47d25a --- /dev/null +++ b/tests/ui/fail/traits/simple_loop_call.rs @@ -0,0 +1,49 @@ +//@error-in-other-file: Unsat +//@compile-flags: -C debug-assertions=off +//@rustc-env: THRUST_SOLVER=tests/thrust-pcsat-wrapper THRUST_SOLVER_TIMEOUT_SECS=60 COAR_IMAGE=coar:latest + +#[thrust_macros::context] +trait A { + #[thrust_macros::requires(Self::p(x))] + #[thrust_macros::ensures(Self::p(result))] + fn f(&self, x: i64) -> i64; + + #[thrust_macros::predicate] + fn p(x: i64) -> bool; +} + +#[thrust_macros::requires(T::p(x))] +#[thrust_macros::ensures(T::p(result))] +fn target(a: &T, x: i64) -> i64 { + let mut v = x; + let mut i = 0; + while i < 3 { + v = a.f(v); + i += 1; + } + + v +} + +#[derive(PartialEq)] +struct B(i64); + +impl thrust_models::Model for B { + type Ty = B; +} + +#[thrust_macros::context] +impl A for B { + fn f(&self, x: i64) -> i64{ + x - 1 + } + + #[thrust_macros::predicate] + fn p(x: i64) -> bool { + "(> x 0)"; true + } +} + +fn main() { + +} From ebfa167dc6c258d76067f46ade84eab350be08ed Mon Sep 17 00:00:00 2001 From: coeff-aij <175928954+coeff-aij@users.noreply.github.com> Date: Sun, 6 Sep 2026 02:30:22 +0900 Subject: [PATCH 117/142] Add probe tests for the language features used by iterator adapters Small, feature-isolated tests (prefix `probe_`) that exercise one pattern each from the Map/Take/Fuse/identity adapter impls, with a trivial trait instead of the Iterator spec, so a failure is attributable to the feature. Each file's header comment states the pattern; FIXME headers state the observed failure. Verified (pass and fail twins): - delegation to a generic field through `&self` and `&mut self` (probe_wrap_delegate_shared, probe_wrap_delegate_mut) - `Option` through a generic wrapper, rebuilt arm by arm and via a catch-all arm (probe_wrap_option_assoc, probe_wrap_option_assoc_catchall) - an unused `F: FnMut` field (probe_generic_impl_two_params) - a usize counter behind `&mut self` (probe_usize_counter); without `-C debug-assertions=off` this hits `unimplemented!` for SubWithOverflow - `Some(ref mut it)` on an Option field (probe_option_field_reborrow_refmut) - calling an `Fn` closure stored in a field (probe_field_closure_call_fn); the field has to be modelled as `model::Closure` and the impl-level `F: Fn` bound repeated on the method, because build_closure_type_for_param only reads the companion function's own predicates. Not verified (FIXME): - probe_field_closure_call: the FnMut version is Unsat. FnMut pre!/post! specs are Unsat on this branch in general (closure_postcondition_fnmut.rs, closure_receiver_mut_model.rs fail the same way); FnOnce and Fn verify. - probe_option_field_reborrow, probe_option_field_reborrow_int: `match &mut self.iter` on an Option field times out (also at 300s), even in a trait-free integer version; the extra Mut layer for the temporary reference is the only difference from the `ref mut` form that verifies. - probe_option_field_reborrow_assign: the same reborrow followed by `self.iter = None` verifies in about 50s on some runs and times out on others. Since delegation, associated-type Option returns, and the counter all verify, the Unsat of the identity adapter (id.rs on iterator-adapters) points at the Iterator::next specification rather than at a language feature. Co-Authored-By: Claude Fable 5.1 --- .../traits/probe_field_closure_call_fn.rs | 28 +++++++++ .../traits/probe_generic_impl_two_params.rs | 50 +++++++++++++++ .../probe_option_field_reborrow_assign.rs | 62 +++++++++++++++++++ .../probe_option_field_reborrow_refmut.rs | 61 ++++++++++++++++++ tests/ui/fail/traits/probe_usize_counter.rs | 32 ++++++++++ .../ui/fail/traits/probe_wrap_delegate_mut.rs | 48 ++++++++++++++ .../fail/traits/probe_wrap_delegate_shared.rs | 50 +++++++++++++++ .../ui/fail/traits/probe_wrap_option_assoc.rs | 56 +++++++++++++++++ .../probe_wrap_option_assoc_catchall.rs | 56 +++++++++++++++++ .../pass/traits/probe_field_closure_call.rs | 31 ++++++++++ .../traits/probe_field_closure_call_fn.rs | 28 +++++++++ .../traits/probe_generic_impl_two_params.rs | 50 +++++++++++++++ .../traits/probe_option_field_reborrow.rs | 61 ++++++++++++++++++ .../probe_option_field_reborrow_assign.rs | 62 +++++++++++++++++++ .../traits/probe_option_field_reborrow_int.rs | 30 +++++++++ .../probe_option_field_reborrow_refmut.rs | 61 ++++++++++++++++++ tests/ui/pass/traits/probe_usize_counter.rs | 32 ++++++++++ .../ui/pass/traits/probe_wrap_delegate_mut.rs | 48 ++++++++++++++ .../pass/traits/probe_wrap_delegate_shared.rs | 50 +++++++++++++++ .../ui/pass/traits/probe_wrap_option_assoc.rs | 56 +++++++++++++++++ .../probe_wrap_option_assoc_catchall.rs | 56 +++++++++++++++++ 21 files changed, 1008 insertions(+) create mode 100644 tests/ui/fail/traits/probe_field_closure_call_fn.rs create mode 100644 tests/ui/fail/traits/probe_generic_impl_two_params.rs create mode 100644 tests/ui/fail/traits/probe_option_field_reborrow_assign.rs create mode 100644 tests/ui/fail/traits/probe_option_field_reborrow_refmut.rs create mode 100644 tests/ui/fail/traits/probe_usize_counter.rs create mode 100644 tests/ui/fail/traits/probe_wrap_delegate_mut.rs create mode 100644 tests/ui/fail/traits/probe_wrap_delegate_shared.rs create mode 100644 tests/ui/fail/traits/probe_wrap_option_assoc.rs create mode 100644 tests/ui/fail/traits/probe_wrap_option_assoc_catchall.rs create mode 100644 tests/ui/pass/traits/probe_field_closure_call.rs create mode 100644 tests/ui/pass/traits/probe_field_closure_call_fn.rs create mode 100644 tests/ui/pass/traits/probe_generic_impl_two_params.rs create mode 100644 tests/ui/pass/traits/probe_option_field_reborrow.rs create mode 100644 tests/ui/pass/traits/probe_option_field_reborrow_assign.rs create mode 100644 tests/ui/pass/traits/probe_option_field_reborrow_int.rs create mode 100644 tests/ui/pass/traits/probe_option_field_reborrow_refmut.rs create mode 100644 tests/ui/pass/traits/probe_usize_counter.rs create mode 100644 tests/ui/pass/traits/probe_wrap_delegate_mut.rs create mode 100644 tests/ui/pass/traits/probe_wrap_delegate_shared.rs create mode 100644 tests/ui/pass/traits/probe_wrap_option_assoc.rs create mode 100644 tests/ui/pass/traits/probe_wrap_option_assoc_catchall.rs diff --git a/tests/ui/fail/traits/probe_field_closure_call_fn.rs b/tests/ui/fail/traits/probe_field_closure_call_fn.rs new file mode 100644 index 00000000..447c0812 --- /dev/null +++ b/tests/ui/fail/traits/probe_field_closure_call_fn.rs @@ -0,0 +1,28 @@ +//@error-in-other-file: Unsat +//@compile-flags: -Aunused_parens -C debug-assertions=off +//@rustc-env: THRUST_SOLVER=tests/thrust-pcsat-wrapper THRUST_SOLVER_TIMEOUT_SECS=60 COAR_IMAGE=coar:latest + +// Probe: calling an `Fn` closure stored in a struct field through `&self`, with pre!/post! on the field. +// The field must be modelled as `model::Closure` for `pre!`/`post!` to accept it as a receiver, +// and the impl-level `F: Fn` bound must be repeated on the method (see probe_field_closure_call.rs). +struct S { + func: F, +} + +impl thrust_models::Model for S { + type Ty = S>; +} + +#[thrust_macros::context] +impl i64> S { + #[thrust_macros::requires(thrust_macros::pre!(((*self).func)(v)))] + #[thrust_macros::ensures(thrust_macros::post!(((*self).func)(v), result))] + fn call(&self, v: i64) -> i64 + where + F: Fn(i64) -> i64, + { + (self.func)(v) + 1 + } +} + +fn main() {} diff --git a/tests/ui/fail/traits/probe_generic_impl_two_params.rs b/tests/ui/fail/traits/probe_generic_impl_two_params.rs new file mode 100644 index 00000000..d6d6cfb6 --- /dev/null +++ b/tests/ui/fail/traits/probe_generic_impl_two_params.rs @@ -0,0 +1,50 @@ +//@error-in-other-file: Unsat +//@compile-flags: -C debug-assertions=off +//@rustc-env: THRUST_SOLVER=tests/thrust-pcsat-wrapper THRUST_SOLVER_TIMEOUT_SECS=60 COAR_IMAGE=coar:latest + +// Probe: a struct with an unused closure-typed field (the Map shape without calling the closure). +#[thrust_macros::context] +trait A { + #[thrust_macros::requires(Self::p(*self))] + #[thrust_macros::ensures(Self::p(!self))] + fn f(&mut self); + + // Same precondition, no postcondition: used by the `fail` twin. + #[thrust_macros::requires(Self::p(*self))] + fn g(&mut self); + + #[thrust_macros::predicate] + fn p(self) -> bool; +} + +struct M { + iter: I, + func: F, +} + +impl thrust_models::Model for M { + type Ty = M; +} + +#[thrust_macros::context] +impl A for M +where + I: A + thrust_models::Model, + ::Ty: PartialEq, + F: FnMut(i64) -> i64, +{ + #[thrust_macros::predicate] + fn p(self) -> bool { + // I::p(self.iter) + "(q_p_f70cecb11526154ed6d8deba1cc45f05 (tuple_proj.0 self_))"; + true + } + + fn g(&mut self) {} + + fn f(&mut self) { + self.iter.g() + } +} + +fn main() {} diff --git a/tests/ui/fail/traits/probe_option_field_reborrow_assign.rs b/tests/ui/fail/traits/probe_option_field_reborrow_assign.rs new file mode 100644 index 00000000..2de65a05 --- /dev/null +++ b/tests/ui/fail/traits/probe_option_field_reborrow_assign.rs @@ -0,0 +1,62 @@ +//@error-in-other-file: Unsat +//@compile-flags: -C debug-assertions=off +//@rustc-env: THRUST_SOLVER=tests/thrust-pcsat-wrapper THRUST_SOLVER_TIMEOUT_SECS=60 COAR_IMAGE=coar:latest + +// Probe: reborrowing an Option field and then overwriting the field (the Fuse pattern). +#[thrust_macros::context] +trait A { + #[thrust_macros::requires(Self::p(*self))] + #[thrust_macros::ensures(Self::p(!self))] + fn f(&mut self); + + // Same precondition, no postcondition: used by the `fail` twin. + #[thrust_macros::requires(Self::p(*self))] + fn g(&mut self); + + #[thrust_macros::predicate] + fn p(self) -> bool; +} + +struct Fz { + iter: Option, +} + +impl thrust_models::Model for Fz { + type Ty = Fz; +} + +#[thrust_macros::context] +impl A for Fz +where + I: A + thrust_models::Model, + ::Ty: PartialEq, +{ + #[thrust_macros::predicate] + fn p(self) -> bool { + // self.iter == None || !I::p(self.iter.unwrap()) (fail: contradicts the precondition of it.f()) + "(or + ((_ is std.option.Option.None) + (tuple_proj>.0 self_)) + (and + ((_ is std.option.Option.Some) + (tuple_proj>.0 self_)) + (not (q_p_f3f493b342eb910838cc97bbb7a143cd + (_getstd.option.Option.Some.0 + (tuple_proj>.0 self_))))))"; + true + } + + fn g(&mut self) {} + + fn f(&mut self) { + match &mut self.iter { + None => {} + Some(it) => { + it.f(); + self.iter = None; + } + } + } +} + +fn main() {} diff --git a/tests/ui/fail/traits/probe_option_field_reborrow_refmut.rs b/tests/ui/fail/traits/probe_option_field_reborrow_refmut.rs new file mode 100644 index 00000000..131aa97e --- /dev/null +++ b/tests/ui/fail/traits/probe_option_field_reborrow_refmut.rs @@ -0,0 +1,61 @@ +//@error-in-other-file: Unsat +//@compile-flags: -C debug-assertions=off +//@rustc-env: THRUST_SOLVER=tests/thrust-pcsat-wrapper THRUST_SOLVER_TIMEOUT_SECS=60 COAR_IMAGE=coar:latest + +// Probe: matching an Option field in place with `Some(ref mut it)` (alternative to `match &mut self.iter`). +#[thrust_macros::context] +trait A { + #[thrust_macros::requires(Self::p(*self))] + #[thrust_macros::ensures(Self::p(!self))] + fn f(&mut self); + + // Same precondition, no postcondition: used by the `fail` twin. + #[thrust_macros::requires(Self::p(*self))] + fn g(&mut self); + + #[thrust_macros::predicate] + fn p(self) -> bool; +} + +struct Fz { + iter: Option, +} + +impl thrust_models::Model for Fz { + type Ty = Fz; +} + +#[thrust_macros::context] +impl A for Fz +where + I: A + thrust_models::Model, + ::Ty: PartialEq, +{ + #[thrust_macros::predicate] + fn p(self) -> bool { + // self.iter == None || !I::p(self.iter.unwrap()) (fail: contradicts the precondition of it.f()) + "(or + ((_ is std.option.Option.None) + (tuple_proj>.0 self_)) + (and + ((_ is std.option.Option.Some) + (tuple_proj>.0 self_)) + (not (q_p_ce73a56b450707e9cffd9dab5bf28cbe + (_getstd.option.Option.Some.0 + (tuple_proj>.0 self_))))))"; + true + } + + fn g(&mut self) {} + + fn f(&mut self) { + match self.iter { + None => {} + Some(ref mut it) => { + it.f(); + } + } + } +} + +fn main() {} diff --git a/tests/ui/fail/traits/probe_usize_counter.rs b/tests/ui/fail/traits/probe_usize_counter.rs new file mode 100644 index 00000000..5bda4a68 --- /dev/null +++ b/tests/ui/fail/traits/probe_usize_counter.rs @@ -0,0 +1,32 @@ +//@error-in-other-file: Unsat +//@compile-flags: -C debug-assertions=off +//@rustc-env: THRUST_SOLVER=tests/thrust-pcsat-wrapper THRUST_SOLVER_TIMEOUT_SECS=60 COAR_IMAGE=coar:latest + +// Probe: a usize counter decremented behind &mut self (the Take counter pattern). +struct C { + n: usize, +} + +impl thrust_models::Model for C { + type Ty = C; +} + +#[thrust_macros::context] +impl C { + #[thrust_macros::ensures(result == true ==> (*self).n != 0 && (!self).n == (*self).n - 1)] + #[thrust_macros::ensures(result == false ==> (*self).n == 0 && (!self).n == 0)] + fn dec(&mut self) -> bool { + if self.n != 0 { + self.n -= 1; + true + } else { + true + } + } +} + +fn main() { + let mut c = C { n: 1 }; + assert!(c.dec()); + assert!(!c.dec()); +} diff --git a/tests/ui/fail/traits/probe_wrap_delegate_mut.rs b/tests/ui/fail/traits/probe_wrap_delegate_mut.rs new file mode 100644 index 00000000..6686653f --- /dev/null +++ b/tests/ui/fail/traits/probe_wrap_delegate_mut.rs @@ -0,0 +1,48 @@ +//@error-in-other-file: Unsat +//@compile-flags: -C debug-assertions=off +//@rustc-env: THRUST_SOLVER=tests/thrust-pcsat-wrapper THRUST_SOLVER_TIMEOUT_SECS=60 COAR_IMAGE=coar:latest + +// Probe: `&mut self` prophecy flowing through a field of generic type (the id.rs pattern). +#[thrust_macros::context] +trait A { + #[thrust_macros::requires(Self::p(*self))] + #[thrust_macros::ensures(Self::p(!self))] + fn f(&mut self); + + // Same precondition, no postcondition: used by the `fail` twin. + #[thrust_macros::requires(Self::p(*self))] + fn g(&mut self); + + #[thrust_macros::predicate] + fn p(self) -> bool; +} + +struct W { + inner: I, +} + +impl thrust_models::Model for W { + type Ty = W; +} + +#[thrust_macros::context] +impl A for W +where + I: A + thrust_models::Model, + ::Ty: PartialEq, +{ + #[thrust_macros::predicate] + fn p(self) -> bool { + // I::p(self.inner) + "(q_p_edd6cc6d74adee2a39e6de2b7317e446 (tuple_proj.0 self_))"; + true + } + + fn g(&mut self) {} + + fn f(&mut self) { + self.inner.g() + } +} + +fn main() {} diff --git a/tests/ui/fail/traits/probe_wrap_delegate_shared.rs b/tests/ui/fail/traits/probe_wrap_delegate_shared.rs new file mode 100644 index 00000000..984ded69 --- /dev/null +++ b/tests/ui/fail/traits/probe_wrap_delegate_shared.rs @@ -0,0 +1,50 @@ +//@error-in-other-file: Unsat +//@compile-flags: -C debug-assertions=off +//@rustc-env: THRUST_SOLVER=tests/thrust-pcsat-wrapper THRUST_SOLVER_TIMEOUT_SECS=60 COAR_IMAGE=coar:latest + +// Probe: delegation through a field of generic type, via a shared reference. +#[thrust_macros::context] +trait A { + #[thrust_macros::requires(Self::p(*self, x))] + #[thrust_macros::ensures(Self::p(*self, result))] + fn f(&self, x: i64) -> i64; + + // Same precondition, no postcondition: used by the `fail` twin. + #[thrust_macros::requires(Self::p(*self, x))] + fn g(&self, x: i64) -> i64; + + #[thrust_macros::predicate] + fn p(self, x: i64) -> bool; +} + +struct W { + inner: I, +} + +impl thrust_models::Model for W { + type Ty = W; +} + +#[thrust_macros::context] +impl A for W +where + I: A + thrust_models::Model, + ::Ty: PartialEq, +{ + #[thrust_macros::predicate] + fn p(self, x: i64) -> bool { + // I::p(self.inner, x) + "(q_p_42c537a71def73a26dafdd620b9ebb1c (tuple_proj.0 self_) x)"; + true + } + + fn g(&self, x: i64) -> i64 { + x + } + + fn f(&self, x: i64) -> i64 { + self.inner.g(x) + } +} + +fn main() {} diff --git a/tests/ui/fail/traits/probe_wrap_option_assoc.rs b/tests/ui/fail/traits/probe_wrap_option_assoc.rs new file mode 100644 index 00000000..4760951c --- /dev/null +++ b/tests/ui/fail/traits/probe_wrap_option_assoc.rs @@ -0,0 +1,56 @@ +//@error-in-other-file: Unsat +//@compile-flags: -C debug-assertions=off +//@rustc-env: THRUST_SOLVER=tests/thrust-pcsat-wrapper THRUST_SOLVER_TIMEOUT_SECS=60 COAR_IMAGE=coar:latest + +// Probe: Option flowing through a generic wrapper, rebuilt arm by arm. +#[thrust_macros::context] +trait A { + type Item; + + #[thrust_macros::ensures(thrust_models::forall(|i| result == Some(i) ==> Self::ok(*self, i)))] + fn get(&mut self) -> Option; + + // No postcondition: used by the `fail` twin. + fn other(&mut self) -> Option; + + #[thrust_macros::predicate] + fn ok(self, i: Self::Item) -> bool; +} + +struct W { + inner: I, +} + +impl thrust_models::Model for W { + type Ty = W; +} + +#[thrust_macros::context] +impl A for W +where + I: A + thrust_models::Model, + ::Item: thrust_models::Model, + ::Ty: PartialEq, +{ + type Item = I::Item; + + #[thrust_macros::predicate] + fn ok(self, i: Self::Item) -> bool { + // I::ok(self.inner, i) + "(q_ok_19223ce020a7c12a9310ccc860ce133e (tuple_proj.0 self_) i)"; + true + } + + fn other(&mut self) -> Option { + self.inner.other() + } + + fn get(&mut self) -> Option { + match self.inner.get() { + Some(_) => self.inner.other(), + None => None, + } + } +} + +fn main() {} diff --git a/tests/ui/fail/traits/probe_wrap_option_assoc_catchall.rs b/tests/ui/fail/traits/probe_wrap_option_assoc_catchall.rs new file mode 100644 index 00000000..4f4c055d --- /dev/null +++ b/tests/ui/fail/traits/probe_wrap_option_assoc_catchall.rs @@ -0,0 +1,56 @@ +//@error-in-other-file: Unsat +//@compile-flags: -C debug-assertions=off +//@rustc-env: THRUST_SOLVER=tests/thrust-pcsat-wrapper THRUST_SOLVER_TIMEOUT_SECS=60 COAR_IMAGE=coar:latest + +// Probe: Option flowing through a generic wrapper, via a catch-all arm (fuse.rs pattern). +#[thrust_macros::context] +trait A { + type Item; + + #[thrust_macros::ensures(thrust_models::forall(|i| result == Some(i) ==> Self::ok(*self, i)))] + fn get(&mut self) -> Option; + + // No postcondition: used by the `fail` twin. + fn other(&mut self) -> Option; + + #[thrust_macros::predicate] + fn ok(self, i: Self::Item) -> bool; +} + +struct W { + inner: I, +} + +impl thrust_models::Model for W { + type Ty = W; +} + +#[thrust_macros::context] +impl A for W +where + I: A + thrust_models::Model, + ::Item: thrust_models::Model, + ::Ty: PartialEq, +{ + type Item = I::Item; + + #[thrust_macros::predicate] + fn ok(self, i: Self::Item) -> bool { + // I::ok(self.inner, i) + "(q_ok_337bddb8da64d77e1943225dec707dbb (tuple_proj.0 self_) i)"; + true + } + + fn other(&mut self) -> Option { + self.inner.other() + } + + fn get(&mut self) -> Option { + match self.inner.get() { + None => None, + _ => self.inner.other(), + } + } +} + +fn main() {} diff --git a/tests/ui/pass/traits/probe_field_closure_call.rs b/tests/ui/pass/traits/probe_field_closure_call.rs new file mode 100644 index 00000000..de99b153 --- /dev/null +++ b/tests/ui/pass/traits/probe_field_closure_call.rs @@ -0,0 +1,31 @@ +// FIXME: Unsat; FnMut closure pre!/post! specs are Unsat branch-wide (closure_postcondition_fnmut.rs fails too). Without the redundant method-level `where F: FnMut` bound Thrust panics at src/analyze/annot_fn.rs:571 ("precondition used on a non-closure parameter"). +//@compile-flags: -C debug-assertions=off +//@rustc-env: THRUST_SOLVER=tests/thrust-pcsat-wrapper THRUST_SOLVER_TIMEOUT_SECS=60 COAR_IMAGE=coar:latest + +// Probe: calling a closure stored in a struct field, with pre!/post! on the field (the Map pattern). +use thrust_models::{exists, model::Mut}; + +struct S { + func: F, +} + +// The field must be modelled as `model::Closure` for `pre!`/`post!` to accept it as a receiver. +impl thrust_models::Model for S { + type Ty = S>; +} + +#[thrust_macros::context] +impl i64> S { + #[thrust_macros::requires(exists(|g| thrust_macros::pre!(Mut::new((*self).func, g)(v))))] + #[thrust_macros::ensures(thrust_macros::post!(Mut::new((*self).func, (!self).func)(v), result))] + // The impl-level `F: FnMut` bound is not seen by `build_closure_type_for_param` + // (src/refine/template.rs:598, own `predicates_of` only); repeat it on the method. + fn call(&mut self, v: i64) -> i64 + where + F: FnMut(i64) -> i64, + { + (self.func)(v) + } +} + +fn main() {} diff --git a/tests/ui/pass/traits/probe_field_closure_call_fn.rs b/tests/ui/pass/traits/probe_field_closure_call_fn.rs new file mode 100644 index 00000000..3cc530a6 --- /dev/null +++ b/tests/ui/pass/traits/probe_field_closure_call_fn.rs @@ -0,0 +1,28 @@ +//@check-pass +//@compile-flags: -Aunused_parens -C debug-assertions=off +//@rustc-env: THRUST_SOLVER=tests/thrust-pcsat-wrapper THRUST_SOLVER_TIMEOUT_SECS=60 COAR_IMAGE=coar:latest + +// Probe: calling an `Fn` closure stored in a struct field through `&self`, with pre!/post! on the field. +// The field must be modelled as `model::Closure` for `pre!`/`post!` to accept it as a receiver, +// and the impl-level `F: Fn` bound must be repeated on the method (see probe_field_closure_call.rs). +struct S { + func: F, +} + +impl thrust_models::Model for S { + type Ty = S>; +} + +#[thrust_macros::context] +impl i64> S { + #[thrust_macros::requires(thrust_macros::pre!(((*self).func)(v)))] + #[thrust_macros::ensures(thrust_macros::post!(((*self).func)(v), result))] + fn call(&self, v: i64) -> i64 + where + F: Fn(i64) -> i64, + { + (self.func)(v) + } +} + +fn main() {} diff --git a/tests/ui/pass/traits/probe_generic_impl_two_params.rs b/tests/ui/pass/traits/probe_generic_impl_two_params.rs new file mode 100644 index 00000000..99ee0a8b --- /dev/null +++ b/tests/ui/pass/traits/probe_generic_impl_two_params.rs @@ -0,0 +1,50 @@ +//@check-pass +//@compile-flags: -C debug-assertions=off +//@rustc-env: THRUST_SOLVER=tests/thrust-pcsat-wrapper THRUST_SOLVER_TIMEOUT_SECS=60 COAR_IMAGE=coar:latest + +// Probe: a struct with an unused closure-typed field (the Map shape without calling the closure). +#[thrust_macros::context] +trait A { + #[thrust_macros::requires(Self::p(*self))] + #[thrust_macros::ensures(Self::p(!self))] + fn f(&mut self); + + // Same precondition, no postcondition: used by the `fail` twin. + #[thrust_macros::requires(Self::p(*self))] + fn g(&mut self); + + #[thrust_macros::predicate] + fn p(self) -> bool; +} + +struct M { + iter: I, + func: F, +} + +impl thrust_models::Model for M { + type Ty = M; +} + +#[thrust_macros::context] +impl A for M +where + I: A + thrust_models::Model, + ::Ty: PartialEq, + F: FnMut(i64) -> i64, +{ + #[thrust_macros::predicate] + fn p(self) -> bool { + // I::p(self.iter) + "(q_p_f70cecb11526154ed6d8deba1cc45f05 (tuple_proj.0 self_))"; + true + } + + fn g(&mut self) {} + + fn f(&mut self) { + self.iter.f() + } +} + +fn main() {} diff --git a/tests/ui/pass/traits/probe_option_field_reborrow.rs b/tests/ui/pass/traits/probe_option_field_reborrow.rs new file mode 100644 index 00000000..72dc69fa --- /dev/null +++ b/tests/ui/pass/traits/probe_option_field_reborrow.rs @@ -0,0 +1,61 @@ +// FIXME: Timeout(60s) from PCSAT (also at 300s); `match &mut self.iter` on an Option field. The `Some(ref mut it)` form and the assign variant verify. +//@compile-flags: -C debug-assertions=off +//@rustc-env: THRUST_SOLVER=tests/thrust-pcsat-wrapper THRUST_SOLVER_TIMEOUT_SECS=60 COAR_IMAGE=coar:latest + +// Probe: reborrowing an Option field with `match &mut self.iter` (the Fuse pattern). +#[thrust_macros::context] +trait A { + #[thrust_macros::requires(Self::p(*self))] + #[thrust_macros::ensures(Self::p(!self))] + fn f(&mut self); + + // Same precondition, no postcondition: used by the `fail` twin. + #[thrust_macros::requires(Self::p(*self))] + fn g(&mut self); + + #[thrust_macros::predicate] + fn p(self) -> bool; +} + +struct Fz { + iter: Option, +} + +impl thrust_models::Model for Fz { + type Ty = Fz; +} + +#[thrust_macros::context] +impl A for Fz +where + I: A + thrust_models::Model, + ::Ty: PartialEq, +{ + #[thrust_macros::predicate] + fn p(self) -> bool { + // self.iter == None || I::p(self.iter.unwrap()) + "(or + ((_ is std.option.Option.None) + (tuple_proj>.0 self_)) + (and + ((_ is std.option.Option.Some) + (tuple_proj>.0 self_)) + (q_p_94601a6d803116c8e4204f321a4d65e6 + (_getstd.option.Option.Some.0 + (tuple_proj>.0 self_)))))"; + true + } + + fn g(&mut self) {} + + fn f(&mut self) { + match &mut self.iter { + None => {} + Some(it) => { + it.f(); + } + } + } +} + +fn main() {} diff --git a/tests/ui/pass/traits/probe_option_field_reborrow_assign.rs b/tests/ui/pass/traits/probe_option_field_reborrow_assign.rs new file mode 100644 index 00000000..77d33624 --- /dev/null +++ b/tests/ui/pass/traits/probe_option_field_reborrow_assign.rs @@ -0,0 +1,62 @@ +// FIXME: flaky with pcsat: verifies in ~50s on some runs, Timeout(180s) on others (match &mut self.iter + reassignment) +//@compile-flags: -C debug-assertions=off +//@rustc-env: THRUST_SOLVER=tests/thrust-pcsat-wrapper THRUST_SOLVER_TIMEOUT_SECS=60 COAR_IMAGE=coar:latest + +// Probe: reborrowing an Option field and then overwriting the field (the Fuse pattern). +#[thrust_macros::context] +trait A { + #[thrust_macros::requires(Self::p(*self))] + #[thrust_macros::ensures(Self::p(!self))] + fn f(&mut self); + + // Same precondition, no postcondition: used by the `fail` twin. + #[thrust_macros::requires(Self::p(*self))] + fn g(&mut self); + + #[thrust_macros::predicate] + fn p(self) -> bool; +} + +struct Fz { + iter: Option, +} + +impl thrust_models::Model for Fz { + type Ty = Fz; +} + +#[thrust_macros::context] +impl A for Fz +where + I: A + thrust_models::Model, + ::Ty: PartialEq, +{ + #[thrust_macros::predicate] + fn p(self) -> bool { + // self.iter == None || I::p(self.iter.unwrap()) + "(or + ((_ is std.option.Option.None) + (tuple_proj>.0 self_)) + (and + ((_ is std.option.Option.Some) + (tuple_proj>.0 self_)) + (q_p_f3f493b342eb910838cc97bbb7a143cd + (_getstd.option.Option.Some.0 + (tuple_proj>.0 self_)))))"; + true + } + + fn g(&mut self) {} + + fn f(&mut self) { + match &mut self.iter { + None => {} + Some(it) => { + it.f(); + self.iter = None; + } + } + } +} + +fn main() {} diff --git a/tests/ui/pass/traits/probe_option_field_reborrow_int.rs b/tests/ui/pass/traits/probe_option_field_reborrow_int.rs new file mode 100644 index 00000000..368a45f3 --- /dev/null +++ b/tests/ui/pass/traits/probe_option_field_reborrow_int.rs @@ -0,0 +1,30 @@ +// FIXME: Timeout(60s) from PCSAT; trait-free minimisation of probe_option_field_reborrow (`match &mut self.iter` on an Option field) +//@compile-flags: -C debug-assertions=off +//@rustc-env: THRUST_SOLVER=tests/thrust-pcsat-wrapper THRUST_SOLVER_TIMEOUT_SECS=60 COAR_IMAGE=coar:latest + +// The same match on a bare `o: &mut Option` parameter verifies in under a second. +use thrust_models::forall; + +struct Fz { + iter: Option, +} + +impl thrust_models::Model for Fz { + type Ty = Fz; +} + +#[thrust_macros::context] +impl Fz { + #[thrust_macros::ensures((*self).iter == None ==> (!self).iter == None)] + #[thrust_macros::ensures(forall(|v| (*self).iter == Some(v) ==> (!self).iter == Some(v + 1)))] + fn inc(&mut self) { + match &mut self.iter { + None => {} + Some(x) => { + *x += 1; + } + } + } +} + +fn main() {} diff --git a/tests/ui/pass/traits/probe_option_field_reborrow_refmut.rs b/tests/ui/pass/traits/probe_option_field_reborrow_refmut.rs new file mode 100644 index 00000000..b0a790c5 --- /dev/null +++ b/tests/ui/pass/traits/probe_option_field_reborrow_refmut.rs @@ -0,0 +1,61 @@ +//@check-pass +//@compile-flags: -C debug-assertions=off +//@rustc-env: THRUST_SOLVER=tests/thrust-pcsat-wrapper THRUST_SOLVER_TIMEOUT_SECS=60 COAR_IMAGE=coar:latest + +// Probe: matching an Option field in place with `Some(ref mut it)` (alternative to `match &mut self.iter`). +#[thrust_macros::context] +trait A { + #[thrust_macros::requires(Self::p(*self))] + #[thrust_macros::ensures(Self::p(!self))] + fn f(&mut self); + + // Same precondition, no postcondition: used by the `fail` twin. + #[thrust_macros::requires(Self::p(*self))] + fn g(&mut self); + + #[thrust_macros::predicate] + fn p(self) -> bool; +} + +struct Fz { + iter: Option, +} + +impl thrust_models::Model for Fz { + type Ty = Fz; +} + +#[thrust_macros::context] +impl A for Fz +where + I: A + thrust_models::Model, + ::Ty: PartialEq, +{ + #[thrust_macros::predicate] + fn p(self) -> bool { + // self.iter == None || I::p(self.iter.unwrap()) + "(or + ((_ is std.option.Option.None) + (tuple_proj>.0 self_)) + (and + ((_ is std.option.Option.Some) + (tuple_proj>.0 self_)) + (q_p_ce73a56b450707e9cffd9dab5bf28cbe + (_getstd.option.Option.Some.0 + (tuple_proj>.0 self_)))))"; + true + } + + fn g(&mut self) {} + + fn f(&mut self) { + match self.iter { + None => {} + Some(ref mut it) => { + it.f(); + } + } + } +} + +fn main() {} diff --git a/tests/ui/pass/traits/probe_usize_counter.rs b/tests/ui/pass/traits/probe_usize_counter.rs new file mode 100644 index 00000000..575b3b3c --- /dev/null +++ b/tests/ui/pass/traits/probe_usize_counter.rs @@ -0,0 +1,32 @@ +//@check-pass +//@compile-flags: -C debug-assertions=off +//@rustc-env: THRUST_SOLVER=tests/thrust-pcsat-wrapper THRUST_SOLVER_TIMEOUT_SECS=60 COAR_IMAGE=coar:latest + +// Probe: a usize counter decremented behind &mut self (the Take counter pattern). +struct C { + n: usize, +} + +impl thrust_models::Model for C { + type Ty = C; +} + +#[thrust_macros::context] +impl C { + #[thrust_macros::ensures(result == true ==> (*self).n != 0 && (!self).n == (*self).n - 1)] + #[thrust_macros::ensures(result == false ==> (*self).n == 0 && (!self).n == 0)] + fn dec(&mut self) -> bool { + if self.n != 0 { + self.n -= 1; + true + } else { + false + } + } +} + +fn main() { + let mut c = C { n: 1 }; + assert!(c.dec()); + assert!(!c.dec()); +} diff --git a/tests/ui/pass/traits/probe_wrap_delegate_mut.rs b/tests/ui/pass/traits/probe_wrap_delegate_mut.rs new file mode 100644 index 00000000..5f197e29 --- /dev/null +++ b/tests/ui/pass/traits/probe_wrap_delegate_mut.rs @@ -0,0 +1,48 @@ +//@check-pass +//@compile-flags: -C debug-assertions=off +//@rustc-env: THRUST_SOLVER=tests/thrust-pcsat-wrapper THRUST_SOLVER_TIMEOUT_SECS=60 COAR_IMAGE=coar:latest + +// Probe: `&mut self` prophecy flowing through a field of generic type (the id.rs pattern). +#[thrust_macros::context] +trait A { + #[thrust_macros::requires(Self::p(*self))] + #[thrust_macros::ensures(Self::p(!self))] + fn f(&mut self); + + // Same precondition, no postcondition: used by the `fail` twin. + #[thrust_macros::requires(Self::p(*self))] + fn g(&mut self); + + #[thrust_macros::predicate] + fn p(self) -> bool; +} + +struct W { + inner: I, +} + +impl thrust_models::Model for W { + type Ty = W; +} + +#[thrust_macros::context] +impl A for W +where + I: A + thrust_models::Model, + ::Ty: PartialEq, +{ + #[thrust_macros::predicate] + fn p(self) -> bool { + // I::p(self.inner) + "(q_p_edd6cc6d74adee2a39e6de2b7317e446 (tuple_proj.0 self_))"; + true + } + + fn g(&mut self) {} + + fn f(&mut self) { + self.inner.f() + } +} + +fn main() {} diff --git a/tests/ui/pass/traits/probe_wrap_delegate_shared.rs b/tests/ui/pass/traits/probe_wrap_delegate_shared.rs new file mode 100644 index 00000000..b7677a18 --- /dev/null +++ b/tests/ui/pass/traits/probe_wrap_delegate_shared.rs @@ -0,0 +1,50 @@ +//@check-pass +//@compile-flags: -C debug-assertions=off +//@rustc-env: THRUST_SOLVER=tests/thrust-pcsat-wrapper THRUST_SOLVER_TIMEOUT_SECS=60 COAR_IMAGE=coar:latest + +// Probe: delegation through a field of generic type, via a shared reference. +#[thrust_macros::context] +trait A { + #[thrust_macros::requires(Self::p(*self, x))] + #[thrust_macros::ensures(Self::p(*self, result))] + fn f(&self, x: i64) -> i64; + + // Same precondition, no postcondition: used by the `fail` twin. + #[thrust_macros::requires(Self::p(*self, x))] + fn g(&self, x: i64) -> i64; + + #[thrust_macros::predicate] + fn p(self, x: i64) -> bool; +} + +struct W { + inner: I, +} + +impl thrust_models::Model for W { + type Ty = W; +} + +#[thrust_macros::context] +impl A for W +where + I: A + thrust_models::Model, + ::Ty: PartialEq, +{ + #[thrust_macros::predicate] + fn p(self, x: i64) -> bool { + // I::p(self.inner, x) + "(q_p_42c537a71def73a26dafdd620b9ebb1c (tuple_proj.0 self_) x)"; + true + } + + fn g(&self, x: i64) -> i64 { + x + } + + fn f(&self, x: i64) -> i64 { + self.inner.f(x) + } +} + +fn main() {} diff --git a/tests/ui/pass/traits/probe_wrap_option_assoc.rs b/tests/ui/pass/traits/probe_wrap_option_assoc.rs new file mode 100644 index 00000000..8b11bd9a --- /dev/null +++ b/tests/ui/pass/traits/probe_wrap_option_assoc.rs @@ -0,0 +1,56 @@ +//@check-pass +//@compile-flags: -C debug-assertions=off +//@rustc-env: THRUST_SOLVER=tests/thrust-pcsat-wrapper THRUST_SOLVER_TIMEOUT_SECS=60 COAR_IMAGE=coar:latest + +// Probe: Option flowing through a generic wrapper, rebuilt arm by arm. +#[thrust_macros::context] +trait A { + type Item; + + #[thrust_macros::ensures(thrust_models::forall(|i| result == Some(i) ==> Self::ok(*self, i)))] + fn get(&mut self) -> Option; + + // No postcondition: used by the `fail` twin. + fn other(&mut self) -> Option; + + #[thrust_macros::predicate] + fn ok(self, i: Self::Item) -> bool; +} + +struct W { + inner: I, +} + +impl thrust_models::Model for W { + type Ty = W; +} + +#[thrust_macros::context] +impl A for W +where + I: A + thrust_models::Model, + ::Item: thrust_models::Model, + ::Ty: PartialEq, +{ + type Item = I::Item; + + #[thrust_macros::predicate] + fn ok(self, i: Self::Item) -> bool { + // I::ok(self.inner, i) + "(q_ok_19223ce020a7c12a9310ccc860ce133e (tuple_proj.0 self_) i)"; + true + } + + fn other(&mut self) -> Option { + self.inner.other() + } + + fn get(&mut self) -> Option { + match self.inner.get() { + Some(v) => Some(v), + None => None, + } + } +} + +fn main() {} diff --git a/tests/ui/pass/traits/probe_wrap_option_assoc_catchall.rs b/tests/ui/pass/traits/probe_wrap_option_assoc_catchall.rs new file mode 100644 index 00000000..bc55fef7 --- /dev/null +++ b/tests/ui/pass/traits/probe_wrap_option_assoc_catchall.rs @@ -0,0 +1,56 @@ +//@check-pass +//@compile-flags: -C debug-assertions=off +//@rustc-env: THRUST_SOLVER=tests/thrust-pcsat-wrapper THRUST_SOLVER_TIMEOUT_SECS=60 COAR_IMAGE=coar:latest + +// Probe: Option flowing through a generic wrapper, via a catch-all arm (fuse.rs pattern). +#[thrust_macros::context] +trait A { + type Item; + + #[thrust_macros::ensures(thrust_models::forall(|i| result == Some(i) ==> Self::ok(*self, i)))] + fn get(&mut self) -> Option; + + // No postcondition: used by the `fail` twin. + fn other(&mut self) -> Option; + + #[thrust_macros::predicate] + fn ok(self, i: Self::Item) -> bool; +} + +struct W { + inner: I, +} + +impl thrust_models::Model for W { + type Ty = W; +} + +#[thrust_macros::context] +impl A for W +where + I: A + thrust_models::Model, + ::Item: thrust_models::Model, + ::Ty: PartialEq, +{ + type Item = I::Item; + + #[thrust_macros::predicate] + fn ok(self, i: Self::Item) -> bool { + // I::ok(self.inner, i) + "(q_ok_337bddb8da64d77e1943225dec707dbb (tuple_proj.0 self_) i)"; + true + } + + fn other(&mut self) -> Option { + self.inner.other() + } + + fn get(&mut self) -> Option { + match self.inner.get() { + None => None, + x => x, + } + } +} + +fn main() {} From cbf05cf5e3cd0d698e3477304cbdf37fd4fce321 Mon Sep 17 00:00:00 2001 From: coeff-aij <175928954+coeff-aij@users.noreply.github.com> Date: Mon, 7 Sep 2026 01:30:36 +0900 Subject: [PATCH 118/142] Mark the match-&mut probes as passing with the rebuilt PCSat With the rebuilt coar:latest image, probe_option_field_reborrow, probe_option_field_reborrow_int and probe_option_field_reborrow_assign verify in about a second each, so the timeout on `match &mut self.iter` was a solver issue rather than a Thrust one. Drop their FIXME headers and add the two missing fail twins: the integer probe adds 2 instead of 1, the trait probe negates the inner predicate in the impl's `p` (swapping `it.f()` for the no-postcondition `it.g()` still times out). The rebuilt image also changes other results: it rejects .smt2 files that use a sort before declaring it (`[sort_of_sexp] undeclared sort "Mut"` on probe_generic_impl_two_params, generic_impl and map_no_closure), and answers `unknown` instead of `unsat` for the fail twins of probe_wrap_delegate_shared and probe_field_closure_call_fn. Co-Authored-By: Claude Fable 5.1 --- .../traits/probe_option_field_reborrow.rs | 61 +++++++++++++++++++ .../traits/probe_option_field_reborrow_int.rs | 30 +++++++++ .../traits/probe_option_field_reborrow.rs | 2 +- .../probe_option_field_reborrow_assign.rs | 2 +- .../traits/probe_option_field_reborrow_int.rs | 2 +- 5 files changed, 94 insertions(+), 3 deletions(-) create mode 100644 tests/ui/fail/traits/probe_option_field_reborrow.rs create mode 100644 tests/ui/fail/traits/probe_option_field_reborrow_int.rs diff --git a/tests/ui/fail/traits/probe_option_field_reborrow.rs b/tests/ui/fail/traits/probe_option_field_reborrow.rs new file mode 100644 index 00000000..d2340997 --- /dev/null +++ b/tests/ui/fail/traits/probe_option_field_reborrow.rs @@ -0,0 +1,61 @@ +//@error-in-other-file: Unsat +//@compile-flags: -C debug-assertions=off +//@rustc-env: THRUST_SOLVER=tests/thrust-pcsat-wrapper THRUST_SOLVER_TIMEOUT_SECS=60 COAR_IMAGE=coar:latest + +// Probe: reborrowing an Option field with `match &mut self.iter` (the Fuse pattern). +#[thrust_macros::context] +trait A { + #[thrust_macros::requires(Self::p(*self))] + #[thrust_macros::ensures(Self::p(!self))] + fn f(&mut self); + + // Same precondition, no postcondition: used by the `fail` twin. + #[thrust_macros::requires(Self::p(*self))] + fn g(&mut self); + + #[thrust_macros::predicate] + fn p(self) -> bool; +} + +struct Fz { + iter: Option, +} + +impl thrust_models::Model for Fz { + type Ty = Fz; +} + +#[thrust_macros::context] +impl A for Fz +where + I: A + thrust_models::Model, + ::Ty: PartialEq, +{ + #[thrust_macros::predicate] + fn p(self) -> bool { + // self.iter == None || !I::p(self.iter.unwrap()) (fail: contradicts the precondition of it.f()) + "(or + ((_ is std.option.Option.None) + (tuple_proj>.0 self_)) + (and + ((_ is std.option.Option.Some) + (tuple_proj>.0 self_)) + (not (q_p_94601a6d803116c8e4204f321a4d65e6 + (_getstd.option.Option.Some.0 + (tuple_proj>.0 self_))))))"; + true + } + + fn g(&mut self) {} + + fn f(&mut self) { + match &mut self.iter { + None => {} + Some(it) => { + it.f(); + } + } + } +} + +fn main() {} diff --git a/tests/ui/fail/traits/probe_option_field_reborrow_int.rs b/tests/ui/fail/traits/probe_option_field_reborrow_int.rs new file mode 100644 index 00000000..842fe429 --- /dev/null +++ b/tests/ui/fail/traits/probe_option_field_reborrow_int.rs @@ -0,0 +1,30 @@ +//@error-in-other-file: Unsat +//@compile-flags: -C debug-assertions=off +//@rustc-env: THRUST_SOLVER=tests/thrust-pcsat-wrapper THRUST_SOLVER_TIMEOUT_SECS=60 COAR_IMAGE=coar:latest + +// The same match on a bare `o: &mut Option` parameter verifies in under a second. +use thrust_models::forall; + +struct Fz { + iter: Option, +} + +impl thrust_models::Model for Fz { + type Ty = Fz; +} + +#[thrust_macros::context] +impl Fz { + #[thrust_macros::ensures((*self).iter == None ==> (!self).iter == None)] + #[thrust_macros::ensures(forall(|v| (*self).iter == Some(v) ==> (!self).iter == Some(v + 1)))] + fn inc(&mut self) { + match &mut self.iter { + None => {} + Some(x) => { + *x += 2; + } + } + } +} + +fn main() {} diff --git a/tests/ui/pass/traits/probe_option_field_reborrow.rs b/tests/ui/pass/traits/probe_option_field_reborrow.rs index 72dc69fa..ad888c41 100644 --- a/tests/ui/pass/traits/probe_option_field_reborrow.rs +++ b/tests/ui/pass/traits/probe_option_field_reborrow.rs @@ -1,4 +1,4 @@ -// FIXME: Timeout(60s) from PCSAT (also at 300s); `match &mut self.iter` on an Option field. The `Some(ref mut it)` form and the assign variant verify. +//@check-pass //@compile-flags: -C debug-assertions=off //@rustc-env: THRUST_SOLVER=tests/thrust-pcsat-wrapper THRUST_SOLVER_TIMEOUT_SECS=60 COAR_IMAGE=coar:latest diff --git a/tests/ui/pass/traits/probe_option_field_reborrow_assign.rs b/tests/ui/pass/traits/probe_option_field_reborrow_assign.rs index 77d33624..b8b093d0 100644 --- a/tests/ui/pass/traits/probe_option_field_reborrow_assign.rs +++ b/tests/ui/pass/traits/probe_option_field_reborrow_assign.rs @@ -1,4 +1,4 @@ -// FIXME: flaky with pcsat: verifies in ~50s on some runs, Timeout(180s) on others (match &mut self.iter + reassignment) +//@check-pass //@compile-flags: -C debug-assertions=off //@rustc-env: THRUST_SOLVER=tests/thrust-pcsat-wrapper THRUST_SOLVER_TIMEOUT_SECS=60 COAR_IMAGE=coar:latest diff --git a/tests/ui/pass/traits/probe_option_field_reborrow_int.rs b/tests/ui/pass/traits/probe_option_field_reborrow_int.rs index 368a45f3..c9024cff 100644 --- a/tests/ui/pass/traits/probe_option_field_reborrow_int.rs +++ b/tests/ui/pass/traits/probe_option_field_reborrow_int.rs @@ -1,4 +1,4 @@ -// FIXME: Timeout(60s) from PCSAT; trait-free minimisation of probe_option_field_reborrow (`match &mut self.iter` on an Option field) +//@check-pass //@compile-flags: -C debug-assertions=off //@rustc-env: THRUST_SOLVER=tests/thrust-pcsat-wrapper THRUST_SOLVER_TIMEOUT_SECS=60 COAR_IMAGE=coar:latest From 1b59fcf874e23260a27fedd7b110d2d97860ac8d Mon Sep 17 00:00:00 2001 From: coeff-aij <175928954+coeff-aij@users.noreply.github.com> Date: Mon, 7 Sep 2026 01:49:54 +0900 Subject: [PATCH 119/142] Find closure bounds declared on the enclosing impl or trait `build_closure_type_for_param` read `predicates_of(fn).predicates`, which holds only the predicates written on the function itself. A closure-typed parameter whose `Fn*` bound sits on the enclosing `impl` (the shape of an iterator adapter storing its mapper) therefore had no function type, and a `pre!`/`post!` on it panicked in `annot_fn` ("precondition used on a non-closure parameter") unless the bound was repeated on the method. Go through `GenericPredicates::instantiate` / `instantiate_identity`, which walk the parent chain, so bounds from the impl or trait header are seen too. probe_field_closure_call_fn.rs drops the redundant method-level bound and still verifies (its fail twin is unchanged); probe_field_closure_call.rs drops it as well and no longer panics (it stays Unsat, see its FIXME). No other closure, fn_poly or traits test changes outcome. Co-Authored-By: Claude Fable 5.1 --- src/refine/template.rs | 23 +++++++++---------- .../traits/probe_field_closure_call_fn.rs | 9 +++----- .../pass/traits/probe_field_closure_call.rs | 9 ++------ .../traits/probe_field_closure_call_fn.rs | 9 +++----- 4 files changed, 19 insertions(+), 31 deletions(-) diff --git a/src/refine/template.rs b/src/refine/template.rs index c75fce3f..41d2b4fb 100644 --- a/src/refine/template.rs +++ b/src/refine/template.rs @@ -595,18 +595,17 @@ impl<'tcx> TypeBuilder<'tcx> { } else { param_ty }; - let mut predicates = self - .tcx - .predicates_of(local_def_id.to_def_id()) - .predicates - .iter() - .map(|(clause, _)| { - if !generic_args.is_empty() { - mir_ty::EarlyBinder::bind(*clause).instantiate(self.tcx, generic_args) - } else { - *clause - } - }); + // `predicates_of(..).predicates` holds only the predicates written on the + // function itself; a bound such as `F: FnMut(..)` on the enclosing impl or + // trait lives in the parent's predicates. `instantiate` and + // `instantiate_identity` walk the parent chain, so go through them. + let generic_predicates = self.tcx.predicates_of(local_def_id.to_def_id()); + let predicates = if !generic_args.is_empty() { + generic_predicates.instantiate(self.tcx, generic_args) + } else { + generic_predicates.instantiate_identity(self.tcx) + }; + let mut predicates = predicates.predicates.into_iter(); let mut params = predicates.clone().find_map(|clause| { self.closure_trait_args(param_ty, clause.as_trait_clause()?.skip_binder()) diff --git a/tests/ui/fail/traits/probe_field_closure_call_fn.rs b/tests/ui/fail/traits/probe_field_closure_call_fn.rs index 447c0812..2c0b4e52 100644 --- a/tests/ui/fail/traits/probe_field_closure_call_fn.rs +++ b/tests/ui/fail/traits/probe_field_closure_call_fn.rs @@ -3,8 +3,8 @@ //@rustc-env: THRUST_SOLVER=tests/thrust-pcsat-wrapper THRUST_SOLVER_TIMEOUT_SECS=60 COAR_IMAGE=coar:latest // Probe: calling an `Fn` closure stored in a struct field through `&self`, with pre!/post! on the field. -// The field must be modelled as `model::Closure` for `pre!`/`post!` to accept it as a receiver, -// and the impl-level `F: Fn` bound must be repeated on the method (see probe_field_closure_call.rs). +// The field must be modelled as `model::Closure` for `pre!`/`post!` to accept it as a receiver. +// The `F: Fn` bound is only on the impl header; `build_closure_type_for_param` has to find it there. struct S { func: F, } @@ -17,10 +17,7 @@ impl thrust_models::Model for S { impl i64> S { #[thrust_macros::requires(thrust_macros::pre!(((*self).func)(v)))] #[thrust_macros::ensures(thrust_macros::post!(((*self).func)(v), result))] - fn call(&self, v: i64) -> i64 - where - F: Fn(i64) -> i64, - { + fn call(&self, v: i64) -> i64 { (self.func)(v) + 1 } } diff --git a/tests/ui/pass/traits/probe_field_closure_call.rs b/tests/ui/pass/traits/probe_field_closure_call.rs index de99b153..9465b87a 100644 --- a/tests/ui/pass/traits/probe_field_closure_call.rs +++ b/tests/ui/pass/traits/probe_field_closure_call.rs @@ -1,4 +1,4 @@ -// FIXME: Unsat; FnMut closure pre!/post! specs are Unsat branch-wide (closure_postcondition_fnmut.rs fails too). Without the redundant method-level `where F: FnMut` bound Thrust panics at src/analyze/annot_fn.rs:571 ("precondition used on a non-closure parameter"). +// FIXME: Unsat; FnMut closure pre!/post! specs are Unsat branch-wide (closure_postcondition_fnmut.rs fails too). //@compile-flags: -C debug-assertions=off //@rustc-env: THRUST_SOLVER=tests/thrust-pcsat-wrapper THRUST_SOLVER_TIMEOUT_SECS=60 COAR_IMAGE=coar:latest @@ -18,12 +18,7 @@ impl thrust_models::Model for S { impl i64> S { #[thrust_macros::requires(exists(|g| thrust_macros::pre!(Mut::new((*self).func, g)(v))))] #[thrust_macros::ensures(thrust_macros::post!(Mut::new((*self).func, (!self).func)(v), result))] - // The impl-level `F: FnMut` bound is not seen by `build_closure_type_for_param` - // (src/refine/template.rs:598, own `predicates_of` only); repeat it on the method. - fn call(&mut self, v: i64) -> i64 - where - F: FnMut(i64) -> i64, - { + fn call(&mut self, v: i64) -> i64 { (self.func)(v) } } diff --git a/tests/ui/pass/traits/probe_field_closure_call_fn.rs b/tests/ui/pass/traits/probe_field_closure_call_fn.rs index 3cc530a6..9a1d964e 100644 --- a/tests/ui/pass/traits/probe_field_closure_call_fn.rs +++ b/tests/ui/pass/traits/probe_field_closure_call_fn.rs @@ -3,8 +3,8 @@ //@rustc-env: THRUST_SOLVER=tests/thrust-pcsat-wrapper THRUST_SOLVER_TIMEOUT_SECS=60 COAR_IMAGE=coar:latest // Probe: calling an `Fn` closure stored in a struct field through `&self`, with pre!/post! on the field. -// The field must be modelled as `model::Closure` for `pre!`/`post!` to accept it as a receiver, -// and the impl-level `F: Fn` bound must be repeated on the method (see probe_field_closure_call.rs). +// The field must be modelled as `model::Closure` for `pre!`/`post!` to accept it as a receiver. +// The `F: Fn` bound is only on the impl header; `build_closure_type_for_param` has to find it there. struct S { func: F, } @@ -17,10 +17,7 @@ impl thrust_models::Model for S { impl i64> S { #[thrust_macros::requires(thrust_macros::pre!(((*self).func)(v)))] #[thrust_macros::ensures(thrust_macros::post!(((*self).func)(v), result))] - fn call(&self, v: i64) -> i64 - where - F: Fn(i64) -> i64, - { + fn call(&self, v: i64) -> i64 { (self.func)(v) } } From 8bb1439b41531d9cb238def24a95e0f8f903368b Mon Sep 17 00:00:00 2001 From: coeff-aij <175928954+coeff-aij@users.noreply.github.com> Date: Mon, 7 Sep 2026 06:01:56 +0900 Subject: [PATCH 120/142] Declare the sorts used only in forall and user-defined predicate signatures `collect_sorts` gathered the sorts to emit as `declare-datatypes` from the predicate-variable signatures and the clauses only. A sort that appears solely in the parameter list of a forall predicate (`declare-forall-fun`) or in the signature of a `#[thrust_macros::predicate]` (`define-fun`) was therefore neither declared nor run through the Hoice renamer, and showed up raw in the output, e.g. `(define-fun p_valid_... ((self_ Tuple) (x a1)) Bool true)` or `(declare-forall-fun q_post_f_... (Mut Int Int) Bool)`. This happens when the predicate is declared for a generic impl but never applied in a clause, as in generic_impl.rs. Older PCSat builds silently accepted these files; the rebuilt one rejects them with `[sort_of_sexp] undeclared sort "Tuple"`. Collect the parameter and type-parameter sorts of every registered `ForallPred` and the signature sorts of every `UserDefinedPredDef` as well, so they are monomorphised, renamed and declared before their first use like every other sort. generic_impl.rs, probe_generic_impl_two_params.rs (pass and fail twins) and fail/loop_invariant_fn_param_closure.rs verify again; the pass twin of the latter now reaches the solver and gets Unsat instead of the parse error. Two unit tests pin the declaration for a forall-pred-only and a user-defined-pred-only sort. Co-Authored-By: Claude Fable 5.1 --- src/chc.rs | 45 +++++++++++++++++++++++++++++++++++++++ src/chc/format_context.rs | 12 +++++++++++ 2 files changed, 57 insertions(+) diff --git a/src/chc.rs b/src/chc.rs index aba859eb..8f517e41 100644 --- a/src/chc.rs +++ b/src/chc.rs @@ -2535,4 +2535,49 @@ mod tests { assert!(smt.contains("; type_param=ParamTy T/#0 (decl=DefId(...))")); assert!(smt.contains("(declare-forall-sort a0)")); } + + #[test] + fn declares_sorts_used_only_in_forall_pred_signatures() { + let mut system = System::default(); + let idx = system.new_forall_sort(DebugInfo::default()); + let tuple = Sort::tuple(vec![Sort::forall(idx), Sort::int()]); + system.register_forall_pred(ForallPred::new( + "q".into(), + vec![Sort::forall(idx)], + vec![tuple, Sort::int()], + )); + + let smt = system.smtlib2().to_string(); + let declared = smt + .find("(A0_Tuple 0)") + .expect("tuple datatype declared"); + let used = smt + .find("(declare-forall-fun q (A0_Tuple Int) Bool)") + .expect("forall pred declared with the renamed sort"); + assert!(declared < used); + assert!(!smt.contains(" Tuple")); + assert!(!smt.contains("(Tuple")); + } + + #[test] + fn declares_sorts_used_only_in_user_defined_pred_signatures() { + let mut system = System::default(); + let tuple = Sort::tuple(vec![Sort::int(), Sort::int()]); + system.push_pred_define( + UserDefinedPred::new("p".into()), + vec![("self_".into(), tuple), ("x".into(), Sort::int())], + "true".into(), + ); + + let smt = system.smtlib2().to_string(); + let declared = smt + .find("(A0_Tuple 0)") + .expect("tuple datatype declared"); + let used = smt + .find("(define-fun p ((self_ A0_Tuple) (x Int)) Bool true)") + .expect("user-defined pred defined with the renamed sort"); + assert!(declared < used); + assert!(!smt.contains(" Tuple")); + assert!(!smt.contains("(Tuple")); + } } diff --git a/src/chc/format_context.rs b/src/chc/format_context.rs index 7a007c0f..a14b97df 100644 --- a/src/chc/format_context.rs +++ b/src/chc/format_context.rs @@ -231,6 +231,18 @@ fn collect_sorts(system: &chc::System) -> BTreeSet { sorts.extend(def.sig.clone()); } + // Forall predicates and user-defined predicates are emitted with their signatures + // (`declare-forall-fun` / `define-fun`), so the sorts appearing there need declaring + // even when no clause mentions them + for pred in &system.forall_pred_vars { + sorts.extend(pred.type_parameters.clone()); + sorts.extend(pred.params.clone()); + } + + for def in &system.user_defined_pred_defs { + sorts.extend(def.sig.iter().map(|(_, sort)| sort.clone())); + } + for clause in &system.clauses { sorts.extend(clause.vars.clone()); atom_sorts(clause, &clause.head, &mut sorts); From 4b4f0ac74da7869b2e969459da93477c2c76e073 Mon Sep 17 00:00:00 2001 From: coeff-aij <175928954+coeff-aij@users.noreply.github.com> Date: Mon, 7 Sep 2026 06:03:21 +0900 Subject: [PATCH 121/142] Resolve trait predicate calls on concrete types in non-generic functions A specification of a function without generics that calls a trait predicate on a concrete type, e.g. `X::p(result)` where `X: A` is a concrete impl, was emitted as a universally quantified forall predicate `q_p_...>` instead of the impl's `define-fun p_p_...` body. Nothing constrains that forall-fun, so the goal clause could never be proved and the file was Unsat regardless of the program. The cause was `instantiate_generics` returning `None` whenever the owner has no generic args: `to_formula_or_term` took that to mean "unresolved" and routed the call to `refine::trait_forall_pred` even though `Instance::try_resolve` had already resolved it to the impl item. In a generic owner the identity args are non-empty, so the same code path resolved correctly, which is why the existing traits tests did not catch it. An empty owner instantiation only means there is nothing to substitute, so treat it as the identity and let `Instance::try_resolve` decide the routing in both cases: a call on a concrete type resolves to the impl's predicate, and a call that still depends on the owner's type parameters (an `ImplSource::Param`) returns `None` and keeps the forall predicate. Add tests/ui/{pass,fail}/traits/concrete_pred_nongeneric.rs pinning this down: a non-generic `target() -> X` with `ensures(X::p(result))` where `p` is `x > 0`, returning `X(1)` (pass) and `X(0)` (fail). The same routing also hit a free `#[thrust::predicate]` called from a non-generic function, so tests/ui/pass/annot_preds.rs verifies again. No other closure, fn_poly, trait or traits/ test changes outcome. Co-Authored-By: Claude Fable 5.1 --- src/analyze/annot_fn.rs | 23 ++++++------ .../fail/traits/concrete_pred_nongeneric.rs | 35 +++++++++++++++++++ .../pass/traits/concrete_pred_nongeneric.rs | 35 +++++++++++++++++++ 3 files changed, 83 insertions(+), 10 deletions(-) create mode 100644 tests/ui/fail/traits/concrete_pred_nongeneric.rs create mode 100644 tests/ui/pass/traits/concrete_pred_nongeneric.rs diff --git a/src/analyze/annot_fn.rs b/src/analyze/annot_fn.rs index 504b6193..66afd579 100644 --- a/src/analyze/annot_fn.rs +++ b/src/analyze/annot_fn.rs @@ -1038,11 +1038,16 @@ impl<'a, 'tcx> AnnotFnTranslator<'a, 'tcx> { outer_generic_args = ?self.generic_args, "resolving predicate call in formula" ); - let (mut is_unresolved_args, generic_args) = - match self.instantiate_generics(generic_args, self.generic_args) { - Some(args) => (false, args), - None => (true, generic_args), - }; + // `self.generic_args` is empty only when the owner has no generics, + // so the predicate's own args are already concrete and there is + // nothing to instantiate. In both cases `Instance::try_resolve` + // decides the routing: it resolves a call on a concrete type to the + // impl's predicate, and returns `None` for a call that still + // depends on the owner's type parameters (an `ImplSource::Param`), + // which is the only case that needs the forall predicate. + let generic_args = self + .instantiate_generics(generic_args, self.generic_args) + .unwrap_or(generic_args); let instance = mir_ty::Instance::try_resolve( self.tcx, @@ -1051,11 +1056,9 @@ impl<'a, 'tcx> AnnotFnTranslator<'a, 'tcx> { generic_args, ) .unwrap(); - let pred_def_id = if let Some(instance) = instance { - instance.def_id() - } else { - is_unresolved_args = true; - def_id + let (is_unresolved_args, pred_def_id) = match instance { + Some(instance) => (false, instance.def_id()), + None => (true, def_id), }; let pred = if is_unresolved_args { diff --git a/tests/ui/fail/traits/concrete_pred_nongeneric.rs b/tests/ui/fail/traits/concrete_pred_nongeneric.rs new file mode 100644 index 00000000..cfae391f --- /dev/null +++ b/tests/ui/fail/traits/concrete_pred_nongeneric.rs @@ -0,0 +1,35 @@ +//@error-in-other-file: Unsat +//@compile-flags: -C debug-assertions=off +//@rustc-env: THRUST_SOLVER=tests/thrust-pcsat-wrapper THRUST_SOLVER_TIMEOUT_SECS=60 COAR_IMAGE=coar:latest + +// A non-generic function's specification calls a trait predicate on a concrete type. The +// call must resolve to the impl's predicate body, not to an unconstrained forall predicate. + +#[thrust_macros::context] +trait A { + #[thrust_macros::predicate] + fn p(self) -> bool; +} + +#[derive(PartialEq)] +struct X(i64); + +impl thrust_models::Model for X { + type Ty = X; +} + +#[thrust_macros::context] +impl A for X { + #[thrust_macros::predicate] + fn p(self) -> bool { + "(> (tuple_proj.0 self_) 0)"; true + } +} + +#[thrust_macros::requires(true)] +#[thrust_macros::ensures(X::p(result))] +fn target() -> X { + X(0) +} + +fn main() {} diff --git a/tests/ui/pass/traits/concrete_pred_nongeneric.rs b/tests/ui/pass/traits/concrete_pred_nongeneric.rs new file mode 100644 index 00000000..becca66c --- /dev/null +++ b/tests/ui/pass/traits/concrete_pred_nongeneric.rs @@ -0,0 +1,35 @@ +//@check-pass +//@compile-flags: -C debug-assertions=off +//@rustc-env: THRUST_SOLVER=tests/thrust-pcsat-wrapper THRUST_SOLVER_TIMEOUT_SECS=60 COAR_IMAGE=coar:latest + +// A non-generic function's specification calls a trait predicate on a concrete type. The +// call must resolve to the impl's predicate body, not to an unconstrained forall predicate. + +#[thrust_macros::context] +trait A { + #[thrust_macros::predicate] + fn p(self) -> bool; +} + +#[derive(PartialEq)] +struct X(i64); + +impl thrust_models::Model for X { + type Ty = X; +} + +#[thrust_macros::context] +impl A for X { + #[thrust_macros::predicate] + fn p(self) -> bool { + "(> (tuple_proj.0 self_) 0)"; true + } +} + +#[thrust_macros::requires(true)] +#[thrust_macros::ensures(X::p(result))] +fn target() -> X { + X(1) +} + +fn main() {} From 1707162d13cbd05679276d0eb40575d815031861 Mon Sep 17 00:00:00 2001 From: coeff-aij <175928954+coeff-aij@users.noreply.github.com> Date: Mon, 7 Sep 2026 06:03:29 +0900 Subject: [PATCH 122/142] Fix the simple_loop_call_multi.rs example and add its fail twin The example was Unsat on its own account, independently of the predicate routing bug fixed in the previous commit: `Y(-1)` violated `repeat`'s precondition `T::p(*x)` at `repeat(&mut y, 5)`. Call `y.g()` first, whose `ensures(Self::p(!self))` establishes the precondition. With both fixed, PCSat still times out inferring the invariant of the loop in the generic `repeat`, so spell it out the way simple_loop_self_mut.rs does: `T::p(*b)` plus the prophecy link `!b == !x.at_entry()`, with `x` rebound to `b` so the invariant can name both the current `&mut` and the entry value. The fail twin tests/ui/fail/traits/simple_loop_call_multi.rs drops the `y.g()` call again. Co-Authored-By: Claude Fable 5.1 --- .../ui/fail/traits/simple_loop_call_multi.rs | 91 +++++++++++++++++++ .../ui/pass/traits/simple_loop_call_multi.rs | 12 ++- 2 files changed, 102 insertions(+), 1 deletion(-) create mode 100644 tests/ui/fail/traits/simple_loop_call_multi.rs diff --git a/tests/ui/fail/traits/simple_loop_call_multi.rs b/tests/ui/fail/traits/simple_loop_call_multi.rs new file mode 100644 index 00000000..d0f6bc17 --- /dev/null +++ b/tests/ui/fail/traits/simple_loop_call_multi.rs @@ -0,0 +1,91 @@ +//@error-in-other-file: Unsat +//@compile-flags: -C debug-assertions=off +//@rustc-env: THRUST_SOLVER=tests/thrust-pcsat-wrapper THRUST_SOLVER_TIMEOUT_SECS=60 COAR_IMAGE=coar:latest + +#[thrust_macros::context] +trait A { + #[thrust_macros::requires(Self::p(*self))] + #[thrust_macros::ensures(Self::p(!self))] + fn f(&mut self); + #[thrust_macros::requires(true)] + #[thrust_macros::ensures(Self::p(!self))] + fn g(&mut self); + + #[thrust_macros::predicate] + fn p(self) -> bool; +} + +// PCSat times out inferring the loop invariant, so it is spelled out as in +// simple_loop_self_mut.rs: `T::p(*b)` plus the prophecy link `!b == !x` between the +// loop's `&mut` and the entry `x`, with `x` rebound to `b` so both can be named. +#[thrust_macros::context] +#[thrust_macros::requires(T::p(*x) && n > 0)] +#[thrust_macros::ensures(T::p(!x))] +fn repeat(x: &mut T, n: u64) { + let b = x; + let mut i = 0; + while i < n { + thrust_macros::invariant!( + |b: &mut T, x: thrust_models::FnParam<&mut T>| T::p(*b) && !b == !x.at_entry() + ); + b.f(); + i += 1; + } +} + +#[derive(PartialEq)] +struct X(i64); + +impl thrust_models::Model for X { + type Ty = X; +} + +#[thrust_macros::context] +impl A for X { + fn f(&mut self) { + self.0 += 1 + } + + fn g(&mut self) { + if !(self.0 > 0) { self.0 = 1 - self.0 } + } + + #[thrust_macros::predicate] + fn p(self) -> bool { + "(> (tuple_proj.0 self_) 0)"; true + } +} + +#[derive(PartialEq)] +struct Y(i64); + +impl thrust_models::Model for Y { + type Ty = Y; +} + +#[thrust_macros::context] +impl A for Y { + fn f(&mut self) { + self.0 += 1 + } + + fn g(&mut self) { + if !(self.0 > 0) { self.0 = 1 - self.0 } + } + + #[thrust_macros::predicate] + fn p(self) -> bool { + "(> (tuple_proj.0 self_) 0)"; true + } +} + +#[thrust_macros::requires(true)] +#[thrust_macros::ensures(X::p(result.0) && Y::p(result.1))] +fn target() -> (X, Y) { + let (mut x, mut y) = (X(1), Y(-1)); + repeat(&mut x, 3); + repeat(&mut y, 5); + (x, y) +} + +fn main() {} diff --git a/tests/ui/pass/traits/simple_loop_call_multi.rs b/tests/ui/pass/traits/simple_loop_call_multi.rs index c17e2caf..33db7799 100644 --- a/tests/ui/pass/traits/simple_loop_call_multi.rs +++ b/tests/ui/pass/traits/simple_loop_call_multi.rs @@ -15,12 +15,20 @@ trait A { fn p(self) -> bool; } +// PCSat times out inferring the loop invariant, so it is spelled out as in +// simple_loop_self_mut.rs: `T::p(*b)` plus the prophecy link `!b == !x` between the +// loop's `&mut` and the entry `x`, with `x` rebound to `b` so both can be named. +#[thrust_macros::context] #[thrust_macros::requires(T::p(*x) && n > 0)] #[thrust_macros::ensures(T::p(!x))] fn repeat(x: &mut T, n: u64) { + let b = x; let mut i = 0; while i < n { - x.f(); + thrust_macros::invariant!( + |b: &mut T, x: thrust_models::FnParam<&mut T>| T::p(*b) && !b == !x.at_entry() + ); + b.f(); i += 1; } } @@ -76,6 +84,8 @@ impl A for Y { fn target() -> (X, Y) { let (mut x, mut y) = (X(1), Y(-1)); repeat(&mut x, 3); + // `Y(-1)` does not satisfy `repeat`'s precondition `T::p(*x)`; `g` establishes it. + y.g(); repeat(&mut y, 5); (x, y) } From 622b67ea4ecb7154e2ac9ddd96bdf21c6e54e6e7 Mon Sep 17 00:00:00 2001 From: coeff-aij <175928954+coeff-aij@users.noreply.github.com> Date: Mon, 7 Sep 2026 06:11:09 +0900 Subject: [PATCH 123/142] Fix simple_loop_self_mut.rs and add the simple_loop_self fail twins simple_loop_self_mut.rs put `requires(Self::p(*self, !self, x))` on `f`, a precondition on the callee's own prophecy. Each `a.f(v)` in the loop reborrows `a` with a fresh, universally quantified final value, so no invariant can establish that for an abstract `p` (the commit adding the file, 52ad0ae, already marked it as not supported). Use the `&mut` analogue of simple_loop_self.rs instead: `p(self, x)` with `requires(Self::p(*self, x))` and `ensures(Self::p(!self, result))`. PCSat still needs the loop invariant spelled out, so the loop carries `invariant!(|b: &mut T, ...| T::p(*b, v) && !b == !a.at_entry())` with `a` rebound to `b`, because an `invariant!` cannot name both views of the same `&mut` argument. Its fail twin uses `v = b.f(v) + 1`. The simple_loop_self twin weakens the precondition to `true`; the rebuilt PCSat refutes it in 0.3s where the previous build timed out. Co-Authored-By: Claude Fable 5.1 --- tests/ui/fail/traits/simple_loop_self.rs | 28 +++++++++++++++ tests/ui/fail/traits/simple_loop_self_mut.rs | 38 ++++++++++++++++++++ tests/ui/pass/traits/simple_loop_self_mut.rs | 26 ++++++++------ 3 files changed, 82 insertions(+), 10 deletions(-) create mode 100644 tests/ui/fail/traits/simple_loop_self.rs create mode 100644 tests/ui/fail/traits/simple_loop_self_mut.rs diff --git a/tests/ui/fail/traits/simple_loop_self.rs b/tests/ui/fail/traits/simple_loop_self.rs new file mode 100644 index 00000000..0f650006 --- /dev/null +++ b/tests/ui/fail/traits/simple_loop_self.rs @@ -0,0 +1,28 @@ +//@error-in-other-file: Unsat +//@compile-flags: -C debug-assertions=off +//@rustc-env: THRUST_SOLVER=tests/thrust-pcsat-wrapper THRUST_SOLVER_TIMEOUT_SECS=60 COAR_IMAGE=coar:latest + +#[thrust_macros::context] +trait A { + #[thrust_macros::requires(Self::p(*self, x))] + #[thrust_macros::ensures(Self::p(*self, result))] + fn f(&self, x: i64) -> i64; + + #[thrust_macros::predicate] + fn p(self, x: i64) -> bool; +} + +#[thrust_macros::requires(true)] +#[thrust_macros::ensures(T::p(*a, result))] +fn target(a: &T, x: i64) -> i64 { + let mut v = x; + let mut i = 0; + while i < 3 { + v = a.f(v); + i += 1; + } + + v +} + +fn main() {} diff --git a/tests/ui/fail/traits/simple_loop_self_mut.rs b/tests/ui/fail/traits/simple_loop_self_mut.rs new file mode 100644 index 00000000..55892316 --- /dev/null +++ b/tests/ui/fail/traits/simple_loop_self_mut.rs @@ -0,0 +1,38 @@ +//@error-in-other-file: Unsat +//@compile-flags: -C debug-assertions=off +//@rustc-env: THRUST_SOLVER=tests/thrust-pcsat-wrapper THRUST_SOLVER_TIMEOUT_SECS=60 COAR_IMAGE=coar:latest + +#[thrust_macros::context] +trait A { + #[thrust_macros::requires(Self::p(*self, x))] + #[thrust_macros::ensures(Self::p(!self, result))] + fn f(&mut self, x: i64) -> i64; + + #[thrust_macros::predicate] + fn p(self, x: i64) -> bool; +} + +// The loop invariant is `T::p(*b, v)` plus the prophecy link `!b == !a` between +// the loop's `&mut` and the entry `a`; PCSat does not infer the latter, so it is +// spelled out. `a` is rebound to `b` because an `invariant!` cannot name both the +// current value and the `FnParam` entry value of the same `&mut` parameter. +#[thrust_macros::context] +#[thrust_macros::requires(T::p(*a, x))] +#[thrust_macros::ensures(T::p(!a, result))] +fn target(a: &mut T, x: i64) -> i64 { + let b = a; + let mut v = x; + let mut i = 0; + while i < 3 { + thrust_macros::invariant!( + |b: &mut T, v: i64, a: thrust_models::FnParam<&mut T>| + T::p(*b, v) && !b == !a.at_entry() + ); + v = b.f(v) + 1; + i += 1; + } + + v +} + +fn main() {} diff --git a/tests/ui/pass/traits/simple_loop_self_mut.rs b/tests/ui/pass/traits/simple_loop_self_mut.rs index 3bd731bd..4c2c0b0f 100644 --- a/tests/ui/pass/traits/simple_loop_self_mut.rs +++ b/tests/ui/pass/traits/simple_loop_self_mut.rs @@ -4,25 +4,31 @@ #[thrust_macros::context] trait A { - #[thrust_macros::requires(Self::p(*self, !self, x))] - #[thrust_macros::ensures(Self::p(*self, !self, result))] + #[thrust_macros::requires(Self::p(*self, x))] + #[thrust_macros::ensures(Self::p(!self, result))] fn f(&mut self, x: i64) -> i64; #[thrust_macros::predicate] - fn p(self, after: Self, x: i64) -> bool; + fn p(self, x: i64) -> bool; } -// impl thrust_models::Model for A { -// type Ty = A; -// } - -#[thrust_macros::requires(T::p(*a, !a, x))] -#[thrust_macros::ensures(T::p(*a, !a, result))] +// The loop invariant is `T::p(*b, v)` plus the prophecy link `!b == !a` between +// the loop's `&mut` and the entry `a`; PCSat does not infer the latter, so it is +// spelled out. `a` is rebound to `b` because an `invariant!` cannot name both the +// current value and the `FnParam` entry value of the same `&mut` parameter. +#[thrust_macros::context] +#[thrust_macros::requires(T::p(*a, x))] +#[thrust_macros::ensures(T::p(!a, result))] fn target(a: &mut T, x: i64) -> i64 { + let b = a; let mut v = x; let mut i = 0; while i < 3 { - v = a.f(v); + thrust_macros::invariant!( + |b: &mut T, v: i64, a: thrust_models::FnParam<&mut T>| + T::p(*b, v) && !b == !a.at_entry() + ); + v = b.f(v); i += 1; } From 323a3b8483ab34b2fd63b5a054fb3e0e15123f33 Mon Sep 17 00:00:00 2001 From: coeff-aij <175928954+coeff-aij@users.noreply.github.com> Date: Tue, 8 Sep 2026 16:44:04 +0900 Subject: [PATCH 124/142] Drop the probe_ prefix from the adapter feature tests The tests keep their header comment describing which adapter pattern each one isolates; the prefix added nothing. Co-Authored-By: Claude Fable 5.1 --- .../{probe_field_closure_call_fn.rs => field_closure_call_fn.rs} | 0 ...robe_generic_impl_two_params.rs => generic_impl_two_params.rs} | 0 .../{probe_option_field_reborrow.rs => option_field_reborrow.rs} | 0 ...n_field_reborrow_assign.rs => option_field_reborrow_assign.rs} | 0 ..._option_field_reborrow_int.rs => option_field_reborrow_int.rs} | 0 ...n_field_reborrow_refmut.rs => option_field_reborrow_refmut.rs} | 0 tests/ui/fail/traits/{probe_usize_counter.rs => usize_counter.rs} | 0 .../traits/{probe_wrap_delegate_mut.rs => wrap_delegate_mut.rs} | 0 .../{probe_wrap_delegate_shared.rs => wrap_delegate_shared.rs} | 0 .../traits/{probe_wrap_option_assoc.rs => wrap_option_assoc.rs} | 0 ...rap_option_assoc_catchall.rs => wrap_option_assoc_catchall.rs} | 0 .../traits/{probe_field_closure_call.rs => field_closure_call.rs} | 0 .../{probe_field_closure_call_fn.rs => field_closure_call_fn.rs} | 0 ...robe_generic_impl_two_params.rs => generic_impl_two_params.rs} | 0 .../{probe_option_field_reborrow.rs => option_field_reborrow.rs} | 0 ...n_field_reborrow_assign.rs => option_field_reborrow_assign.rs} | 0 ..._option_field_reborrow_int.rs => option_field_reborrow_int.rs} | 0 ...n_field_reborrow_refmut.rs => option_field_reborrow_refmut.rs} | 0 tests/ui/pass/traits/{probe_usize_counter.rs => usize_counter.rs} | 0 .../traits/{probe_wrap_delegate_mut.rs => wrap_delegate_mut.rs} | 0 .../{probe_wrap_delegate_shared.rs => wrap_delegate_shared.rs} | 0 .../traits/{probe_wrap_option_assoc.rs => wrap_option_assoc.rs} | 0 ...rap_option_assoc_catchall.rs => wrap_option_assoc_catchall.rs} | 0 23 files changed, 0 insertions(+), 0 deletions(-) rename tests/ui/fail/traits/{probe_field_closure_call_fn.rs => field_closure_call_fn.rs} (100%) rename tests/ui/fail/traits/{probe_generic_impl_two_params.rs => generic_impl_two_params.rs} (100%) rename tests/ui/fail/traits/{probe_option_field_reborrow.rs => option_field_reborrow.rs} (100%) rename tests/ui/fail/traits/{probe_option_field_reborrow_assign.rs => option_field_reborrow_assign.rs} (100%) rename tests/ui/fail/traits/{probe_option_field_reborrow_int.rs => option_field_reborrow_int.rs} (100%) rename tests/ui/fail/traits/{probe_option_field_reborrow_refmut.rs => option_field_reborrow_refmut.rs} (100%) rename tests/ui/fail/traits/{probe_usize_counter.rs => usize_counter.rs} (100%) rename tests/ui/fail/traits/{probe_wrap_delegate_mut.rs => wrap_delegate_mut.rs} (100%) rename tests/ui/fail/traits/{probe_wrap_delegate_shared.rs => wrap_delegate_shared.rs} (100%) rename tests/ui/fail/traits/{probe_wrap_option_assoc.rs => wrap_option_assoc.rs} (100%) rename tests/ui/fail/traits/{probe_wrap_option_assoc_catchall.rs => wrap_option_assoc_catchall.rs} (100%) rename tests/ui/pass/traits/{probe_field_closure_call.rs => field_closure_call.rs} (100%) rename tests/ui/pass/traits/{probe_field_closure_call_fn.rs => field_closure_call_fn.rs} (100%) rename tests/ui/pass/traits/{probe_generic_impl_two_params.rs => generic_impl_two_params.rs} (100%) rename tests/ui/pass/traits/{probe_option_field_reborrow.rs => option_field_reborrow.rs} (100%) rename tests/ui/pass/traits/{probe_option_field_reborrow_assign.rs => option_field_reborrow_assign.rs} (100%) rename tests/ui/pass/traits/{probe_option_field_reborrow_int.rs => option_field_reborrow_int.rs} (100%) rename tests/ui/pass/traits/{probe_option_field_reborrow_refmut.rs => option_field_reborrow_refmut.rs} (100%) rename tests/ui/pass/traits/{probe_usize_counter.rs => usize_counter.rs} (100%) rename tests/ui/pass/traits/{probe_wrap_delegate_mut.rs => wrap_delegate_mut.rs} (100%) rename tests/ui/pass/traits/{probe_wrap_delegate_shared.rs => wrap_delegate_shared.rs} (100%) rename tests/ui/pass/traits/{probe_wrap_option_assoc.rs => wrap_option_assoc.rs} (100%) rename tests/ui/pass/traits/{probe_wrap_option_assoc_catchall.rs => wrap_option_assoc_catchall.rs} (100%) diff --git a/tests/ui/fail/traits/probe_field_closure_call_fn.rs b/tests/ui/fail/traits/field_closure_call_fn.rs similarity index 100% rename from tests/ui/fail/traits/probe_field_closure_call_fn.rs rename to tests/ui/fail/traits/field_closure_call_fn.rs diff --git a/tests/ui/fail/traits/probe_generic_impl_two_params.rs b/tests/ui/fail/traits/generic_impl_two_params.rs similarity index 100% rename from tests/ui/fail/traits/probe_generic_impl_two_params.rs rename to tests/ui/fail/traits/generic_impl_two_params.rs diff --git a/tests/ui/fail/traits/probe_option_field_reborrow.rs b/tests/ui/fail/traits/option_field_reborrow.rs similarity index 100% rename from tests/ui/fail/traits/probe_option_field_reborrow.rs rename to tests/ui/fail/traits/option_field_reborrow.rs diff --git a/tests/ui/fail/traits/probe_option_field_reborrow_assign.rs b/tests/ui/fail/traits/option_field_reborrow_assign.rs similarity index 100% rename from tests/ui/fail/traits/probe_option_field_reborrow_assign.rs rename to tests/ui/fail/traits/option_field_reborrow_assign.rs diff --git a/tests/ui/fail/traits/probe_option_field_reborrow_int.rs b/tests/ui/fail/traits/option_field_reborrow_int.rs similarity index 100% rename from tests/ui/fail/traits/probe_option_field_reborrow_int.rs rename to tests/ui/fail/traits/option_field_reborrow_int.rs diff --git a/tests/ui/fail/traits/probe_option_field_reborrow_refmut.rs b/tests/ui/fail/traits/option_field_reborrow_refmut.rs similarity index 100% rename from tests/ui/fail/traits/probe_option_field_reborrow_refmut.rs rename to tests/ui/fail/traits/option_field_reborrow_refmut.rs diff --git a/tests/ui/fail/traits/probe_usize_counter.rs b/tests/ui/fail/traits/usize_counter.rs similarity index 100% rename from tests/ui/fail/traits/probe_usize_counter.rs rename to tests/ui/fail/traits/usize_counter.rs diff --git a/tests/ui/fail/traits/probe_wrap_delegate_mut.rs b/tests/ui/fail/traits/wrap_delegate_mut.rs similarity index 100% rename from tests/ui/fail/traits/probe_wrap_delegate_mut.rs rename to tests/ui/fail/traits/wrap_delegate_mut.rs diff --git a/tests/ui/fail/traits/probe_wrap_delegate_shared.rs b/tests/ui/fail/traits/wrap_delegate_shared.rs similarity index 100% rename from tests/ui/fail/traits/probe_wrap_delegate_shared.rs rename to tests/ui/fail/traits/wrap_delegate_shared.rs diff --git a/tests/ui/fail/traits/probe_wrap_option_assoc.rs b/tests/ui/fail/traits/wrap_option_assoc.rs similarity index 100% rename from tests/ui/fail/traits/probe_wrap_option_assoc.rs rename to tests/ui/fail/traits/wrap_option_assoc.rs diff --git a/tests/ui/fail/traits/probe_wrap_option_assoc_catchall.rs b/tests/ui/fail/traits/wrap_option_assoc_catchall.rs similarity index 100% rename from tests/ui/fail/traits/probe_wrap_option_assoc_catchall.rs rename to tests/ui/fail/traits/wrap_option_assoc_catchall.rs diff --git a/tests/ui/pass/traits/probe_field_closure_call.rs b/tests/ui/pass/traits/field_closure_call.rs similarity index 100% rename from tests/ui/pass/traits/probe_field_closure_call.rs rename to tests/ui/pass/traits/field_closure_call.rs diff --git a/tests/ui/pass/traits/probe_field_closure_call_fn.rs b/tests/ui/pass/traits/field_closure_call_fn.rs similarity index 100% rename from tests/ui/pass/traits/probe_field_closure_call_fn.rs rename to tests/ui/pass/traits/field_closure_call_fn.rs diff --git a/tests/ui/pass/traits/probe_generic_impl_two_params.rs b/tests/ui/pass/traits/generic_impl_two_params.rs similarity index 100% rename from tests/ui/pass/traits/probe_generic_impl_two_params.rs rename to tests/ui/pass/traits/generic_impl_two_params.rs diff --git a/tests/ui/pass/traits/probe_option_field_reborrow.rs b/tests/ui/pass/traits/option_field_reborrow.rs similarity index 100% rename from tests/ui/pass/traits/probe_option_field_reborrow.rs rename to tests/ui/pass/traits/option_field_reborrow.rs diff --git a/tests/ui/pass/traits/probe_option_field_reborrow_assign.rs b/tests/ui/pass/traits/option_field_reborrow_assign.rs similarity index 100% rename from tests/ui/pass/traits/probe_option_field_reborrow_assign.rs rename to tests/ui/pass/traits/option_field_reborrow_assign.rs diff --git a/tests/ui/pass/traits/probe_option_field_reborrow_int.rs b/tests/ui/pass/traits/option_field_reborrow_int.rs similarity index 100% rename from tests/ui/pass/traits/probe_option_field_reborrow_int.rs rename to tests/ui/pass/traits/option_field_reborrow_int.rs diff --git a/tests/ui/pass/traits/probe_option_field_reborrow_refmut.rs b/tests/ui/pass/traits/option_field_reborrow_refmut.rs similarity index 100% rename from tests/ui/pass/traits/probe_option_field_reborrow_refmut.rs rename to tests/ui/pass/traits/option_field_reborrow_refmut.rs diff --git a/tests/ui/pass/traits/probe_usize_counter.rs b/tests/ui/pass/traits/usize_counter.rs similarity index 100% rename from tests/ui/pass/traits/probe_usize_counter.rs rename to tests/ui/pass/traits/usize_counter.rs diff --git a/tests/ui/pass/traits/probe_wrap_delegate_mut.rs b/tests/ui/pass/traits/wrap_delegate_mut.rs similarity index 100% rename from tests/ui/pass/traits/probe_wrap_delegate_mut.rs rename to tests/ui/pass/traits/wrap_delegate_mut.rs diff --git a/tests/ui/pass/traits/probe_wrap_delegate_shared.rs b/tests/ui/pass/traits/wrap_delegate_shared.rs similarity index 100% rename from tests/ui/pass/traits/probe_wrap_delegate_shared.rs rename to tests/ui/pass/traits/wrap_delegate_shared.rs diff --git a/tests/ui/pass/traits/probe_wrap_option_assoc.rs b/tests/ui/pass/traits/wrap_option_assoc.rs similarity index 100% rename from tests/ui/pass/traits/probe_wrap_option_assoc.rs rename to tests/ui/pass/traits/wrap_option_assoc.rs diff --git a/tests/ui/pass/traits/probe_wrap_option_assoc_catchall.rs b/tests/ui/pass/traits/wrap_option_assoc_catchall.rs similarity index 100% rename from tests/ui/pass/traits/probe_wrap_option_assoc_catchall.rs rename to tests/ui/pass/traits/wrap_option_assoc_catchall.rs From d7ddf815ff1cbeb7b9d8751a2b95f7b44e97583c Mon Sep 17 00:00:00 2001 From: coeff-aij <175928954+coeff-aij@users.noreply.github.com> Date: Tue, 8 Sep 2026 17:06:40 +0900 Subject: [PATCH 125/142] Pin impl predicate resolution on a generic ADT from a generic owner A generic function's spec calling ` as Foo>::valid(..)`, the predicate of a generic impl on a type that still contains the owner's type parameter, resolves through `Instance::try_resolve` to the impl item and uses its `define-fun` body; only a call on the type parameter itself (`T::valid`) falls back to a forall predicate. This is the intended behaviour of the routing changed in 4b4f0ac; the pair records it (both the qualified-path and the `Bar::::valid` spelling resolve the same way). Co-Authored-By: Claude Fable 5.1 --- .../fail/traits/impl_pred_on_generic_adt.rs | 46 +++++++++++++++++++ .../pass/traits/impl_pred_on_generic_adt.rs | 46 +++++++++++++++++++ 2 files changed, 92 insertions(+) create mode 100644 tests/ui/fail/traits/impl_pred_on_generic_adt.rs create mode 100644 tests/ui/pass/traits/impl_pred_on_generic_adt.rs diff --git a/tests/ui/fail/traits/impl_pred_on_generic_adt.rs b/tests/ui/fail/traits/impl_pred_on_generic_adt.rs new file mode 100644 index 00000000..6b59ee33 --- /dev/null +++ b/tests/ui/fail/traits/impl_pred_on_generic_adt.rs @@ -0,0 +1,46 @@ +//@error-in-other-file: Unsat +//@compile-flags: -C debug-assertions=off +//@rustc-env: THRUST_SOLVER=tests/thrust-pcsat-wrapper THRUST_SOLVER_TIMEOUT_SECS=60 COAR_IMAGE=coar:latest + +// A generic function whose spec calls the predicate of a generic impl on a type that +// still contains the type parameter (` as Foo>::valid`). `Instance::try_resolve` +// resolves this to the impl item, so the impl's `define-fun` body is used, not a forall +// predicate; only a call on the type parameter itself (`T::valid`) needs the latter. +use thrust_models::Model; + +#[thrust_macros::context] +trait Foo { + #[thrust_macros::predicate] + fn valid(self, x: i64) -> bool; +} + +#[derive(PartialEq)] +struct Bar(T); + +impl Model for Bar { + type Ty = Bar; +} + +#[thrust_macros::context] +impl Foo for Bar +where + T: Foo + Model + PartialEq, + ::Ty: PartialEq, +{ + #[thrust_macros::predicate] + fn valid(self, x: i64) -> bool { + "(> x 0)"; true + } +} + +#[thrust_macros::requires(true)] +#[thrust_macros::ensures( as Foo>::valid(result, v))] +fn keep(b: Bar, v: i64) -> Bar +where + T: Foo + Model + PartialEq, + ::Ty: PartialEq, +{ + b +} + +fn main() {} diff --git a/tests/ui/pass/traits/impl_pred_on_generic_adt.rs b/tests/ui/pass/traits/impl_pred_on_generic_adt.rs new file mode 100644 index 00000000..d34e2d58 --- /dev/null +++ b/tests/ui/pass/traits/impl_pred_on_generic_adt.rs @@ -0,0 +1,46 @@ +//@check-pass +//@compile-flags: -C debug-assertions=off +//@rustc-env: THRUST_SOLVER=tests/thrust-pcsat-wrapper THRUST_SOLVER_TIMEOUT_SECS=60 COAR_IMAGE=coar:latest + +// A generic function whose spec calls the predicate of a generic impl on a type that +// still contains the type parameter (` as Foo>::valid`). `Instance::try_resolve` +// resolves this to the impl item, so the impl's `define-fun` body is used, not a forall +// predicate; only a call on the type parameter itself (`T::valid`) needs the latter. +use thrust_models::Model; + +#[thrust_macros::context] +trait Foo { + #[thrust_macros::predicate] + fn valid(self, x: i64) -> bool; +} + +#[derive(PartialEq)] +struct Bar(T); + +impl Model for Bar { + type Ty = Bar; +} + +#[thrust_macros::context] +impl Foo for Bar +where + T: Foo + Model + PartialEq, + ::Ty: PartialEq, +{ + #[thrust_macros::predicate] + fn valid(self, x: i64) -> bool { + "(> x 0)"; true + } +} + +#[thrust_macros::requires( as Foo>::valid(b, v))] +#[thrust_macros::ensures( as Foo>::valid(result, v))] +fn keep(b: Bar, v: i64) -> Bar +where + T: Foo + Model + PartialEq, + ::Ty: PartialEq, +{ + b +} + +fn main() {} From 7828fb11c1be24ed49ffa429a49573dc0b139b20 Mon Sep 17 00:00:00 2001 From: coeff-aij <175928954+coeff-aij@users.noreply.github.com> Date: Tue, 8 Sep 2026 18:47:13 +0900 Subject: [PATCH 126/142] Regenerate the predicate hashes broken by dropping the probe_ prefix The impl predicate bodies of these tests are SMT strings that refer to the inner type's trait predicates by their hash-suffixed names (`q_p_`). The hash comes from the def path, which includes the crate name and hence the file name, so 323a3b8 left every reference stale and PCSat failed with `q_p_ is not bound`. Regenerated with .experimental/extract-predicate-hashes.py for the eight affected pairs. Co-Authored-By: Claude Fable 5.1 --- tests/ui/fail/traits/generic_impl_two_params.rs | 2 +- tests/ui/fail/traits/option_field_reborrow.rs | 2 +- tests/ui/fail/traits/option_field_reborrow_assign.rs | 2 +- tests/ui/fail/traits/option_field_reborrow_refmut.rs | 2 +- tests/ui/fail/traits/wrap_delegate_mut.rs | 2 +- tests/ui/fail/traits/wrap_delegate_shared.rs | 2 +- tests/ui/fail/traits/wrap_option_assoc.rs | 2 +- tests/ui/fail/traits/wrap_option_assoc_catchall.rs | 2 +- tests/ui/pass/traits/generic_impl_two_params.rs | 2 +- tests/ui/pass/traits/option_field_reborrow.rs | 2 +- tests/ui/pass/traits/option_field_reborrow_assign.rs | 2 +- tests/ui/pass/traits/option_field_reborrow_refmut.rs | 2 +- tests/ui/pass/traits/wrap_delegate_mut.rs | 2 +- tests/ui/pass/traits/wrap_delegate_shared.rs | 2 +- tests/ui/pass/traits/wrap_option_assoc.rs | 2 +- tests/ui/pass/traits/wrap_option_assoc_catchall.rs | 2 +- 16 files changed, 16 insertions(+), 16 deletions(-) diff --git a/tests/ui/fail/traits/generic_impl_two_params.rs b/tests/ui/fail/traits/generic_impl_two_params.rs index d6d6cfb6..cf807b7e 100644 --- a/tests/ui/fail/traits/generic_impl_two_params.rs +++ b/tests/ui/fail/traits/generic_impl_two_params.rs @@ -36,7 +36,7 @@ where #[thrust_macros::predicate] fn p(self) -> bool { // I::p(self.iter) - "(q_p_f70cecb11526154ed6d8deba1cc45f05 (tuple_proj.0 self_))"; + "(q_p_90849721ed499bdbe024ffd3cd1c5364 (tuple_proj.0 self_))"; true } diff --git a/tests/ui/fail/traits/option_field_reborrow.rs b/tests/ui/fail/traits/option_field_reborrow.rs index d2340997..5a94e2aa 100644 --- a/tests/ui/fail/traits/option_field_reborrow.rs +++ b/tests/ui/fail/traits/option_field_reborrow.rs @@ -40,7 +40,7 @@ where (and ((_ is std.option.Option.Some) (tuple_proj>.0 self_)) - (not (q_p_94601a6d803116c8e4204f321a4d65e6 + (not (q_p_b169b456844da80e4f3b63d5812aa29c (_getstd.option.Option.Some.0 (tuple_proj>.0 self_))))))"; true diff --git a/tests/ui/fail/traits/option_field_reborrow_assign.rs b/tests/ui/fail/traits/option_field_reborrow_assign.rs index 2de65a05..5f0f8e4d 100644 --- a/tests/ui/fail/traits/option_field_reborrow_assign.rs +++ b/tests/ui/fail/traits/option_field_reborrow_assign.rs @@ -40,7 +40,7 @@ where (and ((_ is std.option.Option.Some) (tuple_proj>.0 self_)) - (not (q_p_f3f493b342eb910838cc97bbb7a143cd + (not (q_p_4a4688a7e8150671723414f2215c50b5 (_getstd.option.Option.Some.0 (tuple_proj>.0 self_))))))"; true diff --git a/tests/ui/fail/traits/option_field_reborrow_refmut.rs b/tests/ui/fail/traits/option_field_reborrow_refmut.rs index 131aa97e..c50fb055 100644 --- a/tests/ui/fail/traits/option_field_reborrow_refmut.rs +++ b/tests/ui/fail/traits/option_field_reborrow_refmut.rs @@ -40,7 +40,7 @@ where (and ((_ is std.option.Option.Some) (tuple_proj>.0 self_)) - (not (q_p_ce73a56b450707e9cffd9dab5bf28cbe + (not (q_p_6d2843ad30bd6272db33fffc235e2912 (_getstd.option.Option.Some.0 (tuple_proj>.0 self_))))))"; true diff --git a/tests/ui/fail/traits/wrap_delegate_mut.rs b/tests/ui/fail/traits/wrap_delegate_mut.rs index 6686653f..2188d203 100644 --- a/tests/ui/fail/traits/wrap_delegate_mut.rs +++ b/tests/ui/fail/traits/wrap_delegate_mut.rs @@ -34,7 +34,7 @@ where #[thrust_macros::predicate] fn p(self) -> bool { // I::p(self.inner) - "(q_p_edd6cc6d74adee2a39e6de2b7317e446 (tuple_proj.0 self_))"; + "(q_p_39ba461a1ee0ac85e4d6462c04277d68 (tuple_proj.0 self_))"; true } diff --git a/tests/ui/fail/traits/wrap_delegate_shared.rs b/tests/ui/fail/traits/wrap_delegate_shared.rs index 984ded69..0a54c0e7 100644 --- a/tests/ui/fail/traits/wrap_delegate_shared.rs +++ b/tests/ui/fail/traits/wrap_delegate_shared.rs @@ -34,7 +34,7 @@ where #[thrust_macros::predicate] fn p(self, x: i64) -> bool { // I::p(self.inner, x) - "(q_p_42c537a71def73a26dafdd620b9ebb1c (tuple_proj.0 self_) x)"; + "(q_p_76f0c568ed2435da625b3e40bc133c46 (tuple_proj.0 self_) x)"; true } diff --git a/tests/ui/fail/traits/wrap_option_assoc.rs b/tests/ui/fail/traits/wrap_option_assoc.rs index 4760951c..a4835eb5 100644 --- a/tests/ui/fail/traits/wrap_option_assoc.rs +++ b/tests/ui/fail/traits/wrap_option_assoc.rs @@ -37,7 +37,7 @@ where #[thrust_macros::predicate] fn ok(self, i: Self::Item) -> bool { // I::ok(self.inner, i) - "(q_ok_19223ce020a7c12a9310ccc860ce133e (tuple_proj.0 self_) i)"; + "(q_ok_200bb7d187270ed1be2cc56b0bc48aad (tuple_proj.0 self_) i)"; true } diff --git a/tests/ui/fail/traits/wrap_option_assoc_catchall.rs b/tests/ui/fail/traits/wrap_option_assoc_catchall.rs index 4f4c055d..8fbcf4b9 100644 --- a/tests/ui/fail/traits/wrap_option_assoc_catchall.rs +++ b/tests/ui/fail/traits/wrap_option_assoc_catchall.rs @@ -37,7 +37,7 @@ where #[thrust_macros::predicate] fn ok(self, i: Self::Item) -> bool { // I::ok(self.inner, i) - "(q_ok_337bddb8da64d77e1943225dec707dbb (tuple_proj.0 self_) i)"; + "(q_ok_b9f69d98bde4a0eaae50a022705885a1 (tuple_proj.0 self_) i)"; true } diff --git a/tests/ui/pass/traits/generic_impl_two_params.rs b/tests/ui/pass/traits/generic_impl_two_params.rs index 99ee0a8b..566e0fc1 100644 --- a/tests/ui/pass/traits/generic_impl_two_params.rs +++ b/tests/ui/pass/traits/generic_impl_two_params.rs @@ -36,7 +36,7 @@ where #[thrust_macros::predicate] fn p(self) -> bool { // I::p(self.iter) - "(q_p_f70cecb11526154ed6d8deba1cc45f05 (tuple_proj.0 self_))"; + "(q_p_90849721ed499bdbe024ffd3cd1c5364 (tuple_proj.0 self_))"; true } diff --git a/tests/ui/pass/traits/option_field_reborrow.rs b/tests/ui/pass/traits/option_field_reborrow.rs index ad888c41..5a92739f 100644 --- a/tests/ui/pass/traits/option_field_reborrow.rs +++ b/tests/ui/pass/traits/option_field_reborrow.rs @@ -40,7 +40,7 @@ where (and ((_ is std.option.Option.Some) (tuple_proj>.0 self_)) - (q_p_94601a6d803116c8e4204f321a4d65e6 + (q_p_b169b456844da80e4f3b63d5812aa29c (_getstd.option.Option.Some.0 (tuple_proj>.0 self_)))))"; true diff --git a/tests/ui/pass/traits/option_field_reborrow_assign.rs b/tests/ui/pass/traits/option_field_reborrow_assign.rs index b8b093d0..f799c702 100644 --- a/tests/ui/pass/traits/option_field_reborrow_assign.rs +++ b/tests/ui/pass/traits/option_field_reborrow_assign.rs @@ -40,7 +40,7 @@ where (and ((_ is std.option.Option.Some) (tuple_proj>.0 self_)) - (q_p_f3f493b342eb910838cc97bbb7a143cd + (q_p_4a4688a7e8150671723414f2215c50b5 (_getstd.option.Option.Some.0 (tuple_proj>.0 self_)))))"; true diff --git a/tests/ui/pass/traits/option_field_reborrow_refmut.rs b/tests/ui/pass/traits/option_field_reborrow_refmut.rs index b0a790c5..38717ef4 100644 --- a/tests/ui/pass/traits/option_field_reborrow_refmut.rs +++ b/tests/ui/pass/traits/option_field_reborrow_refmut.rs @@ -40,7 +40,7 @@ where (and ((_ is std.option.Option.Some) (tuple_proj>.0 self_)) - (q_p_ce73a56b450707e9cffd9dab5bf28cbe + (q_p_6d2843ad30bd6272db33fffc235e2912 (_getstd.option.Option.Some.0 (tuple_proj>.0 self_)))))"; true diff --git a/tests/ui/pass/traits/wrap_delegate_mut.rs b/tests/ui/pass/traits/wrap_delegate_mut.rs index 5f197e29..10e3caa1 100644 --- a/tests/ui/pass/traits/wrap_delegate_mut.rs +++ b/tests/ui/pass/traits/wrap_delegate_mut.rs @@ -34,7 +34,7 @@ where #[thrust_macros::predicate] fn p(self) -> bool { // I::p(self.inner) - "(q_p_edd6cc6d74adee2a39e6de2b7317e446 (tuple_proj.0 self_))"; + "(q_p_39ba461a1ee0ac85e4d6462c04277d68 (tuple_proj.0 self_))"; true } diff --git a/tests/ui/pass/traits/wrap_delegate_shared.rs b/tests/ui/pass/traits/wrap_delegate_shared.rs index b7677a18..a530edd1 100644 --- a/tests/ui/pass/traits/wrap_delegate_shared.rs +++ b/tests/ui/pass/traits/wrap_delegate_shared.rs @@ -34,7 +34,7 @@ where #[thrust_macros::predicate] fn p(self, x: i64) -> bool { // I::p(self.inner, x) - "(q_p_42c537a71def73a26dafdd620b9ebb1c (tuple_proj.0 self_) x)"; + "(q_p_76f0c568ed2435da625b3e40bc133c46 (tuple_proj.0 self_) x)"; true } diff --git a/tests/ui/pass/traits/wrap_option_assoc.rs b/tests/ui/pass/traits/wrap_option_assoc.rs index 8b11bd9a..8b9c33dd 100644 --- a/tests/ui/pass/traits/wrap_option_assoc.rs +++ b/tests/ui/pass/traits/wrap_option_assoc.rs @@ -37,7 +37,7 @@ where #[thrust_macros::predicate] fn ok(self, i: Self::Item) -> bool { // I::ok(self.inner, i) - "(q_ok_19223ce020a7c12a9310ccc860ce133e (tuple_proj.0 self_) i)"; + "(q_ok_200bb7d187270ed1be2cc56b0bc48aad (tuple_proj.0 self_) i)"; true } diff --git a/tests/ui/pass/traits/wrap_option_assoc_catchall.rs b/tests/ui/pass/traits/wrap_option_assoc_catchall.rs index bc55fef7..30b84fbc 100644 --- a/tests/ui/pass/traits/wrap_option_assoc_catchall.rs +++ b/tests/ui/pass/traits/wrap_option_assoc_catchall.rs @@ -37,7 +37,7 @@ where #[thrust_macros::predicate] fn ok(self, i: Self::Item) -> bool { // I::ok(self.inner, i) - "(q_ok_337bddb8da64d77e1943225dec707dbb (tuple_proj.0 self_) i)"; + "(q_ok_b9f69d98bde4a0eaae50a022705885a1 (tuple_proj.0 self_) i)"; true } From cfcb2c5732acdb6daa591a5783c3cdd7bc84d1ed Mon Sep 17 00:00:00 2001 From: coeff-aij <175928954+coeff-aij@users.noreply.github.com> Date: Tue, 8 Sep 2026 19:25:13 +0900 Subject: [PATCH 127/142] Trim the comments of the adapter feature tests The file, type and function names say what each test isolates; drop the "Probe:" headers and the inline explanations, keep the one-line Rust renderings of the SMT predicate bodies, and shorten the one remaining FIXME. Remove option_field_reborrow_refmut: it only worked around a solver timeout that the current PCSat build no longer has and matches no adapter pattern. Co-Authored-By: Claude Fable 5.1 --- .../fail/traits/concrete_pred_nongeneric.rs | 3 - tests/ui/fail/traits/field_closure_call_fn.rs | 3 - .../ui/fail/traits/generic_impl_two_params.rs | 2 - .../fail/traits/impl_pred_on_generic_adt.rs | 4 -- tests/ui/fail/traits/option_field_reborrow.rs | 4 +- .../traits/option_field_reborrow_assign.rs | 4 +- .../fail/traits/option_field_reborrow_int.rs | 1 - .../traits/option_field_reborrow_refmut.rs | 61 ------------------- .../ui/fail/traits/simple_loop_call_multi.rs | 3 - tests/ui/fail/traits/simple_loop_self_mut.rs | 4 -- tests/ui/fail/traits/usize_counter.rs | 1 - tests/ui/fail/traits/wrap_delegate_mut.rs | 2 - tests/ui/fail/traits/wrap_delegate_shared.rs | 2 - tests/ui/fail/traits/wrap_option_assoc.rs | 2 - .../fail/traits/wrap_option_assoc_catchall.rs | 2 - .../pass/traits/concrete_pred_nongeneric.rs | 3 - tests/ui/pass/traits/field_closure_call.rs | 4 +- tests/ui/pass/traits/field_closure_call_fn.rs | 3 - .../ui/pass/traits/generic_impl_two_params.rs | 2 - .../pass/traits/impl_pred_on_generic_adt.rs | 4 -- tests/ui/pass/traits/option_field_reborrow.rs | 2 - .../traits/option_field_reborrow_assign.rs | 2 - .../pass/traits/option_field_reborrow_int.rs | 1 - .../traits/option_field_reborrow_refmut.rs | 61 ------------------- .../ui/pass/traits/simple_loop_call_multi.rs | 4 -- tests/ui/pass/traits/simple_loop_self_mut.rs | 4 -- tests/ui/pass/traits/usize_counter.rs | 1 - tests/ui/pass/traits/wrap_delegate_mut.rs | 2 - tests/ui/pass/traits/wrap_delegate_shared.rs | 2 - tests/ui/pass/traits/wrap_option_assoc.rs | 2 - .../pass/traits/wrap_option_assoc_catchall.rs | 2 - 31 files changed, 3 insertions(+), 194 deletions(-) delete mode 100644 tests/ui/fail/traits/option_field_reborrow_refmut.rs delete mode 100644 tests/ui/pass/traits/option_field_reborrow_refmut.rs diff --git a/tests/ui/fail/traits/concrete_pred_nongeneric.rs b/tests/ui/fail/traits/concrete_pred_nongeneric.rs index cfae391f..f16ce96b 100644 --- a/tests/ui/fail/traits/concrete_pred_nongeneric.rs +++ b/tests/ui/fail/traits/concrete_pred_nongeneric.rs @@ -2,9 +2,6 @@ //@compile-flags: -C debug-assertions=off //@rustc-env: THRUST_SOLVER=tests/thrust-pcsat-wrapper THRUST_SOLVER_TIMEOUT_SECS=60 COAR_IMAGE=coar:latest -// A non-generic function's specification calls a trait predicate on a concrete type. The -// call must resolve to the impl's predicate body, not to an unconstrained forall predicate. - #[thrust_macros::context] trait A { #[thrust_macros::predicate] diff --git a/tests/ui/fail/traits/field_closure_call_fn.rs b/tests/ui/fail/traits/field_closure_call_fn.rs index 2c0b4e52..6046097c 100644 --- a/tests/ui/fail/traits/field_closure_call_fn.rs +++ b/tests/ui/fail/traits/field_closure_call_fn.rs @@ -2,9 +2,6 @@ //@compile-flags: -Aunused_parens -C debug-assertions=off //@rustc-env: THRUST_SOLVER=tests/thrust-pcsat-wrapper THRUST_SOLVER_TIMEOUT_SECS=60 COAR_IMAGE=coar:latest -// Probe: calling an `Fn` closure stored in a struct field through `&self`, with pre!/post! on the field. -// The field must be modelled as `model::Closure` for `pre!`/`post!` to accept it as a receiver. -// The `F: Fn` bound is only on the impl header; `build_closure_type_for_param` has to find it there. struct S { func: F, } diff --git a/tests/ui/fail/traits/generic_impl_two_params.rs b/tests/ui/fail/traits/generic_impl_two_params.rs index cf807b7e..f4fb5435 100644 --- a/tests/ui/fail/traits/generic_impl_two_params.rs +++ b/tests/ui/fail/traits/generic_impl_two_params.rs @@ -2,14 +2,12 @@ //@compile-flags: -C debug-assertions=off //@rustc-env: THRUST_SOLVER=tests/thrust-pcsat-wrapper THRUST_SOLVER_TIMEOUT_SECS=60 COAR_IMAGE=coar:latest -// Probe: a struct with an unused closure-typed field (the Map shape without calling the closure). #[thrust_macros::context] trait A { #[thrust_macros::requires(Self::p(*self))] #[thrust_macros::ensures(Self::p(!self))] fn f(&mut self); - // Same precondition, no postcondition: used by the `fail` twin. #[thrust_macros::requires(Self::p(*self))] fn g(&mut self); diff --git a/tests/ui/fail/traits/impl_pred_on_generic_adt.rs b/tests/ui/fail/traits/impl_pred_on_generic_adt.rs index 6b59ee33..871d8d99 100644 --- a/tests/ui/fail/traits/impl_pred_on_generic_adt.rs +++ b/tests/ui/fail/traits/impl_pred_on_generic_adt.rs @@ -2,10 +2,6 @@ //@compile-flags: -C debug-assertions=off //@rustc-env: THRUST_SOLVER=tests/thrust-pcsat-wrapper THRUST_SOLVER_TIMEOUT_SECS=60 COAR_IMAGE=coar:latest -// A generic function whose spec calls the predicate of a generic impl on a type that -// still contains the type parameter (` as Foo>::valid`). `Instance::try_resolve` -// resolves this to the impl item, so the impl's `define-fun` body is used, not a forall -// predicate; only a call on the type parameter itself (`T::valid`) needs the latter. use thrust_models::Model; #[thrust_macros::context] diff --git a/tests/ui/fail/traits/option_field_reborrow.rs b/tests/ui/fail/traits/option_field_reborrow.rs index 5a94e2aa..02231c83 100644 --- a/tests/ui/fail/traits/option_field_reborrow.rs +++ b/tests/ui/fail/traits/option_field_reborrow.rs @@ -2,14 +2,12 @@ //@compile-flags: -C debug-assertions=off //@rustc-env: THRUST_SOLVER=tests/thrust-pcsat-wrapper THRUST_SOLVER_TIMEOUT_SECS=60 COAR_IMAGE=coar:latest -// Probe: reborrowing an Option field with `match &mut self.iter` (the Fuse pattern). #[thrust_macros::context] trait A { #[thrust_macros::requires(Self::p(*self))] #[thrust_macros::ensures(Self::p(!self))] fn f(&mut self); - // Same precondition, no postcondition: used by the `fail` twin. #[thrust_macros::requires(Self::p(*self))] fn g(&mut self); @@ -33,7 +31,7 @@ where { #[thrust_macros::predicate] fn p(self) -> bool { - // self.iter == None || !I::p(self.iter.unwrap()) (fail: contradicts the precondition of it.f()) + // self.iter == None || !I::p(self.iter.unwrap()) "(or ((_ is std.option.Option.None) (tuple_proj>.0 self_)) diff --git a/tests/ui/fail/traits/option_field_reborrow_assign.rs b/tests/ui/fail/traits/option_field_reborrow_assign.rs index 5f0f8e4d..d147132f 100644 --- a/tests/ui/fail/traits/option_field_reborrow_assign.rs +++ b/tests/ui/fail/traits/option_field_reborrow_assign.rs @@ -2,14 +2,12 @@ //@compile-flags: -C debug-assertions=off //@rustc-env: THRUST_SOLVER=tests/thrust-pcsat-wrapper THRUST_SOLVER_TIMEOUT_SECS=60 COAR_IMAGE=coar:latest -// Probe: reborrowing an Option field and then overwriting the field (the Fuse pattern). #[thrust_macros::context] trait A { #[thrust_macros::requires(Self::p(*self))] #[thrust_macros::ensures(Self::p(!self))] fn f(&mut self); - // Same precondition, no postcondition: used by the `fail` twin. #[thrust_macros::requires(Self::p(*self))] fn g(&mut self); @@ -33,7 +31,7 @@ where { #[thrust_macros::predicate] fn p(self) -> bool { - // self.iter == None || !I::p(self.iter.unwrap()) (fail: contradicts the precondition of it.f()) + // self.iter == None || !I::p(self.iter.unwrap()) "(or ((_ is std.option.Option.None) (tuple_proj>.0 self_)) diff --git a/tests/ui/fail/traits/option_field_reborrow_int.rs b/tests/ui/fail/traits/option_field_reborrow_int.rs index 842fe429..9384b7f1 100644 --- a/tests/ui/fail/traits/option_field_reborrow_int.rs +++ b/tests/ui/fail/traits/option_field_reborrow_int.rs @@ -2,7 +2,6 @@ //@compile-flags: -C debug-assertions=off //@rustc-env: THRUST_SOLVER=tests/thrust-pcsat-wrapper THRUST_SOLVER_TIMEOUT_SECS=60 COAR_IMAGE=coar:latest -// The same match on a bare `o: &mut Option` parameter verifies in under a second. use thrust_models::forall; struct Fz { diff --git a/tests/ui/fail/traits/option_field_reborrow_refmut.rs b/tests/ui/fail/traits/option_field_reborrow_refmut.rs deleted file mode 100644 index c50fb055..00000000 --- a/tests/ui/fail/traits/option_field_reborrow_refmut.rs +++ /dev/null @@ -1,61 +0,0 @@ -//@error-in-other-file: Unsat -//@compile-flags: -C debug-assertions=off -//@rustc-env: THRUST_SOLVER=tests/thrust-pcsat-wrapper THRUST_SOLVER_TIMEOUT_SECS=60 COAR_IMAGE=coar:latest - -// Probe: matching an Option field in place with `Some(ref mut it)` (alternative to `match &mut self.iter`). -#[thrust_macros::context] -trait A { - #[thrust_macros::requires(Self::p(*self))] - #[thrust_macros::ensures(Self::p(!self))] - fn f(&mut self); - - // Same precondition, no postcondition: used by the `fail` twin. - #[thrust_macros::requires(Self::p(*self))] - fn g(&mut self); - - #[thrust_macros::predicate] - fn p(self) -> bool; -} - -struct Fz { - iter: Option, -} - -impl thrust_models::Model for Fz { - type Ty = Fz; -} - -#[thrust_macros::context] -impl A for Fz -where - I: A + thrust_models::Model, - ::Ty: PartialEq, -{ - #[thrust_macros::predicate] - fn p(self) -> bool { - // self.iter == None || !I::p(self.iter.unwrap()) (fail: contradicts the precondition of it.f()) - "(or - ((_ is std.option.Option.None) - (tuple_proj>.0 self_)) - (and - ((_ is std.option.Option.Some) - (tuple_proj>.0 self_)) - (not (q_p_6d2843ad30bd6272db33fffc235e2912 - (_getstd.option.Option.Some.0 - (tuple_proj>.0 self_))))))"; - true - } - - fn g(&mut self) {} - - fn f(&mut self) { - match self.iter { - None => {} - Some(ref mut it) => { - it.f(); - } - } - } -} - -fn main() {} diff --git a/tests/ui/fail/traits/simple_loop_call_multi.rs b/tests/ui/fail/traits/simple_loop_call_multi.rs index d0f6bc17..36888e0f 100644 --- a/tests/ui/fail/traits/simple_loop_call_multi.rs +++ b/tests/ui/fail/traits/simple_loop_call_multi.rs @@ -15,9 +15,6 @@ trait A { fn p(self) -> bool; } -// PCSat times out inferring the loop invariant, so it is spelled out as in -// simple_loop_self_mut.rs: `T::p(*b)` plus the prophecy link `!b == !x` between the -// loop's `&mut` and the entry `x`, with `x` rebound to `b` so both can be named. #[thrust_macros::context] #[thrust_macros::requires(T::p(*x) && n > 0)] #[thrust_macros::ensures(T::p(!x))] diff --git a/tests/ui/fail/traits/simple_loop_self_mut.rs b/tests/ui/fail/traits/simple_loop_self_mut.rs index 55892316..7c0d3d2b 100644 --- a/tests/ui/fail/traits/simple_loop_self_mut.rs +++ b/tests/ui/fail/traits/simple_loop_self_mut.rs @@ -12,10 +12,6 @@ trait A { fn p(self, x: i64) -> bool; } -// The loop invariant is `T::p(*b, v)` plus the prophecy link `!b == !a` between -// the loop's `&mut` and the entry `a`; PCSat does not infer the latter, so it is -// spelled out. `a` is rebound to `b` because an `invariant!` cannot name both the -// current value and the `FnParam` entry value of the same `&mut` parameter. #[thrust_macros::context] #[thrust_macros::requires(T::p(*a, x))] #[thrust_macros::ensures(T::p(!a, result))] diff --git a/tests/ui/fail/traits/usize_counter.rs b/tests/ui/fail/traits/usize_counter.rs index 5bda4a68..79cf32e8 100644 --- a/tests/ui/fail/traits/usize_counter.rs +++ b/tests/ui/fail/traits/usize_counter.rs @@ -2,7 +2,6 @@ //@compile-flags: -C debug-assertions=off //@rustc-env: THRUST_SOLVER=tests/thrust-pcsat-wrapper THRUST_SOLVER_TIMEOUT_SECS=60 COAR_IMAGE=coar:latest -// Probe: a usize counter decremented behind &mut self (the Take counter pattern). struct C { n: usize, } diff --git a/tests/ui/fail/traits/wrap_delegate_mut.rs b/tests/ui/fail/traits/wrap_delegate_mut.rs index 2188d203..617eca18 100644 --- a/tests/ui/fail/traits/wrap_delegate_mut.rs +++ b/tests/ui/fail/traits/wrap_delegate_mut.rs @@ -2,14 +2,12 @@ //@compile-flags: -C debug-assertions=off //@rustc-env: THRUST_SOLVER=tests/thrust-pcsat-wrapper THRUST_SOLVER_TIMEOUT_SECS=60 COAR_IMAGE=coar:latest -// Probe: `&mut self` prophecy flowing through a field of generic type (the id.rs pattern). #[thrust_macros::context] trait A { #[thrust_macros::requires(Self::p(*self))] #[thrust_macros::ensures(Self::p(!self))] fn f(&mut self); - // Same precondition, no postcondition: used by the `fail` twin. #[thrust_macros::requires(Self::p(*self))] fn g(&mut self); diff --git a/tests/ui/fail/traits/wrap_delegate_shared.rs b/tests/ui/fail/traits/wrap_delegate_shared.rs index 0a54c0e7..fbd10c6b 100644 --- a/tests/ui/fail/traits/wrap_delegate_shared.rs +++ b/tests/ui/fail/traits/wrap_delegate_shared.rs @@ -2,14 +2,12 @@ //@compile-flags: -C debug-assertions=off //@rustc-env: THRUST_SOLVER=tests/thrust-pcsat-wrapper THRUST_SOLVER_TIMEOUT_SECS=60 COAR_IMAGE=coar:latest -// Probe: delegation through a field of generic type, via a shared reference. #[thrust_macros::context] trait A { #[thrust_macros::requires(Self::p(*self, x))] #[thrust_macros::ensures(Self::p(*self, result))] fn f(&self, x: i64) -> i64; - // Same precondition, no postcondition: used by the `fail` twin. #[thrust_macros::requires(Self::p(*self, x))] fn g(&self, x: i64) -> i64; diff --git a/tests/ui/fail/traits/wrap_option_assoc.rs b/tests/ui/fail/traits/wrap_option_assoc.rs index a4835eb5..a7134b1d 100644 --- a/tests/ui/fail/traits/wrap_option_assoc.rs +++ b/tests/ui/fail/traits/wrap_option_assoc.rs @@ -2,7 +2,6 @@ //@compile-flags: -C debug-assertions=off //@rustc-env: THRUST_SOLVER=tests/thrust-pcsat-wrapper THRUST_SOLVER_TIMEOUT_SECS=60 COAR_IMAGE=coar:latest -// Probe: Option flowing through a generic wrapper, rebuilt arm by arm. #[thrust_macros::context] trait A { type Item; @@ -10,7 +9,6 @@ trait A { #[thrust_macros::ensures(thrust_models::forall(|i| result == Some(i) ==> Self::ok(*self, i)))] fn get(&mut self) -> Option; - // No postcondition: used by the `fail` twin. fn other(&mut self) -> Option; #[thrust_macros::predicate] diff --git a/tests/ui/fail/traits/wrap_option_assoc_catchall.rs b/tests/ui/fail/traits/wrap_option_assoc_catchall.rs index 8fbcf4b9..645a7afd 100644 --- a/tests/ui/fail/traits/wrap_option_assoc_catchall.rs +++ b/tests/ui/fail/traits/wrap_option_assoc_catchall.rs @@ -2,7 +2,6 @@ //@compile-flags: -C debug-assertions=off //@rustc-env: THRUST_SOLVER=tests/thrust-pcsat-wrapper THRUST_SOLVER_TIMEOUT_SECS=60 COAR_IMAGE=coar:latest -// Probe: Option flowing through a generic wrapper, via a catch-all arm (fuse.rs pattern). #[thrust_macros::context] trait A { type Item; @@ -10,7 +9,6 @@ trait A { #[thrust_macros::ensures(thrust_models::forall(|i| result == Some(i) ==> Self::ok(*self, i)))] fn get(&mut self) -> Option; - // No postcondition: used by the `fail` twin. fn other(&mut self) -> Option; #[thrust_macros::predicate] diff --git a/tests/ui/pass/traits/concrete_pred_nongeneric.rs b/tests/ui/pass/traits/concrete_pred_nongeneric.rs index becca66c..8f71e1f1 100644 --- a/tests/ui/pass/traits/concrete_pred_nongeneric.rs +++ b/tests/ui/pass/traits/concrete_pred_nongeneric.rs @@ -2,9 +2,6 @@ //@compile-flags: -C debug-assertions=off //@rustc-env: THRUST_SOLVER=tests/thrust-pcsat-wrapper THRUST_SOLVER_TIMEOUT_SECS=60 COAR_IMAGE=coar:latest -// A non-generic function's specification calls a trait predicate on a concrete type. The -// call must resolve to the impl's predicate body, not to an unconstrained forall predicate. - #[thrust_macros::context] trait A { #[thrust_macros::predicate] diff --git a/tests/ui/pass/traits/field_closure_call.rs b/tests/ui/pass/traits/field_closure_call.rs index 9465b87a..a92e2df6 100644 --- a/tests/ui/pass/traits/field_closure_call.rs +++ b/tests/ui/pass/traits/field_closure_call.rs @@ -1,15 +1,13 @@ -// FIXME: Unsat; FnMut closure pre!/post! specs are Unsat branch-wide (closure_postcondition_fnmut.rs fails too). +// FIXME: Unsat; an FnMut `pre!` on this branch has to hold for a fresh prophecy of the closure state. //@compile-flags: -C debug-assertions=off //@rustc-env: THRUST_SOLVER=tests/thrust-pcsat-wrapper THRUST_SOLVER_TIMEOUT_SECS=60 COAR_IMAGE=coar:latest -// Probe: calling a closure stored in a struct field, with pre!/post! on the field (the Map pattern). use thrust_models::{exists, model::Mut}; struct S { func: F, } -// The field must be modelled as `model::Closure` for `pre!`/`post!` to accept it as a receiver. impl thrust_models::Model for S { type Ty = S>; } diff --git a/tests/ui/pass/traits/field_closure_call_fn.rs b/tests/ui/pass/traits/field_closure_call_fn.rs index 9a1d964e..e557daaa 100644 --- a/tests/ui/pass/traits/field_closure_call_fn.rs +++ b/tests/ui/pass/traits/field_closure_call_fn.rs @@ -2,9 +2,6 @@ //@compile-flags: -Aunused_parens -C debug-assertions=off //@rustc-env: THRUST_SOLVER=tests/thrust-pcsat-wrapper THRUST_SOLVER_TIMEOUT_SECS=60 COAR_IMAGE=coar:latest -// Probe: calling an `Fn` closure stored in a struct field through `&self`, with pre!/post! on the field. -// The field must be modelled as `model::Closure` for `pre!`/`post!` to accept it as a receiver. -// The `F: Fn` bound is only on the impl header; `build_closure_type_for_param` has to find it there. struct S { func: F, } diff --git a/tests/ui/pass/traits/generic_impl_two_params.rs b/tests/ui/pass/traits/generic_impl_two_params.rs index 566e0fc1..aaf61b9a 100644 --- a/tests/ui/pass/traits/generic_impl_two_params.rs +++ b/tests/ui/pass/traits/generic_impl_two_params.rs @@ -2,14 +2,12 @@ //@compile-flags: -C debug-assertions=off //@rustc-env: THRUST_SOLVER=tests/thrust-pcsat-wrapper THRUST_SOLVER_TIMEOUT_SECS=60 COAR_IMAGE=coar:latest -// Probe: a struct with an unused closure-typed field (the Map shape without calling the closure). #[thrust_macros::context] trait A { #[thrust_macros::requires(Self::p(*self))] #[thrust_macros::ensures(Self::p(!self))] fn f(&mut self); - // Same precondition, no postcondition: used by the `fail` twin. #[thrust_macros::requires(Self::p(*self))] fn g(&mut self); diff --git a/tests/ui/pass/traits/impl_pred_on_generic_adt.rs b/tests/ui/pass/traits/impl_pred_on_generic_adt.rs index d34e2d58..3d2ad86f 100644 --- a/tests/ui/pass/traits/impl_pred_on_generic_adt.rs +++ b/tests/ui/pass/traits/impl_pred_on_generic_adt.rs @@ -2,10 +2,6 @@ //@compile-flags: -C debug-assertions=off //@rustc-env: THRUST_SOLVER=tests/thrust-pcsat-wrapper THRUST_SOLVER_TIMEOUT_SECS=60 COAR_IMAGE=coar:latest -// A generic function whose spec calls the predicate of a generic impl on a type that -// still contains the type parameter (` as Foo>::valid`). `Instance::try_resolve` -// resolves this to the impl item, so the impl's `define-fun` body is used, not a forall -// predicate; only a call on the type parameter itself (`T::valid`) needs the latter. use thrust_models::Model; #[thrust_macros::context] diff --git a/tests/ui/pass/traits/option_field_reborrow.rs b/tests/ui/pass/traits/option_field_reborrow.rs index 5a92739f..875533e4 100644 --- a/tests/ui/pass/traits/option_field_reborrow.rs +++ b/tests/ui/pass/traits/option_field_reborrow.rs @@ -2,14 +2,12 @@ //@compile-flags: -C debug-assertions=off //@rustc-env: THRUST_SOLVER=tests/thrust-pcsat-wrapper THRUST_SOLVER_TIMEOUT_SECS=60 COAR_IMAGE=coar:latest -// Probe: reborrowing an Option field with `match &mut self.iter` (the Fuse pattern). #[thrust_macros::context] trait A { #[thrust_macros::requires(Self::p(*self))] #[thrust_macros::ensures(Self::p(!self))] fn f(&mut self); - // Same precondition, no postcondition: used by the `fail` twin. #[thrust_macros::requires(Self::p(*self))] fn g(&mut self); diff --git a/tests/ui/pass/traits/option_field_reborrow_assign.rs b/tests/ui/pass/traits/option_field_reborrow_assign.rs index f799c702..733cefb5 100644 --- a/tests/ui/pass/traits/option_field_reborrow_assign.rs +++ b/tests/ui/pass/traits/option_field_reborrow_assign.rs @@ -2,14 +2,12 @@ //@compile-flags: -C debug-assertions=off //@rustc-env: THRUST_SOLVER=tests/thrust-pcsat-wrapper THRUST_SOLVER_TIMEOUT_SECS=60 COAR_IMAGE=coar:latest -// Probe: reborrowing an Option field and then overwriting the field (the Fuse pattern). #[thrust_macros::context] trait A { #[thrust_macros::requires(Self::p(*self))] #[thrust_macros::ensures(Self::p(!self))] fn f(&mut self); - // Same precondition, no postcondition: used by the `fail` twin. #[thrust_macros::requires(Self::p(*self))] fn g(&mut self); diff --git a/tests/ui/pass/traits/option_field_reborrow_int.rs b/tests/ui/pass/traits/option_field_reborrow_int.rs index c9024cff..0ccd778a 100644 --- a/tests/ui/pass/traits/option_field_reborrow_int.rs +++ b/tests/ui/pass/traits/option_field_reborrow_int.rs @@ -2,7 +2,6 @@ //@compile-flags: -C debug-assertions=off //@rustc-env: THRUST_SOLVER=tests/thrust-pcsat-wrapper THRUST_SOLVER_TIMEOUT_SECS=60 COAR_IMAGE=coar:latest -// The same match on a bare `o: &mut Option` parameter verifies in under a second. use thrust_models::forall; struct Fz { diff --git a/tests/ui/pass/traits/option_field_reborrow_refmut.rs b/tests/ui/pass/traits/option_field_reborrow_refmut.rs deleted file mode 100644 index 38717ef4..00000000 --- a/tests/ui/pass/traits/option_field_reborrow_refmut.rs +++ /dev/null @@ -1,61 +0,0 @@ -//@check-pass -//@compile-flags: -C debug-assertions=off -//@rustc-env: THRUST_SOLVER=tests/thrust-pcsat-wrapper THRUST_SOLVER_TIMEOUT_SECS=60 COAR_IMAGE=coar:latest - -// Probe: matching an Option field in place with `Some(ref mut it)` (alternative to `match &mut self.iter`). -#[thrust_macros::context] -trait A { - #[thrust_macros::requires(Self::p(*self))] - #[thrust_macros::ensures(Self::p(!self))] - fn f(&mut self); - - // Same precondition, no postcondition: used by the `fail` twin. - #[thrust_macros::requires(Self::p(*self))] - fn g(&mut self); - - #[thrust_macros::predicate] - fn p(self) -> bool; -} - -struct Fz { - iter: Option, -} - -impl thrust_models::Model for Fz { - type Ty = Fz; -} - -#[thrust_macros::context] -impl A for Fz -where - I: A + thrust_models::Model, - ::Ty: PartialEq, -{ - #[thrust_macros::predicate] - fn p(self) -> bool { - // self.iter == None || I::p(self.iter.unwrap()) - "(or - ((_ is std.option.Option.None) - (tuple_proj>.0 self_)) - (and - ((_ is std.option.Option.Some) - (tuple_proj>.0 self_)) - (q_p_6d2843ad30bd6272db33fffc235e2912 - (_getstd.option.Option.Some.0 - (tuple_proj>.0 self_)))))"; - true - } - - fn g(&mut self) {} - - fn f(&mut self) { - match self.iter { - None => {} - Some(ref mut it) => { - it.f(); - } - } - } -} - -fn main() {} diff --git a/tests/ui/pass/traits/simple_loop_call_multi.rs b/tests/ui/pass/traits/simple_loop_call_multi.rs index 33db7799..c6660e43 100644 --- a/tests/ui/pass/traits/simple_loop_call_multi.rs +++ b/tests/ui/pass/traits/simple_loop_call_multi.rs @@ -15,9 +15,6 @@ trait A { fn p(self) -> bool; } -// PCSat times out inferring the loop invariant, so it is spelled out as in -// simple_loop_self_mut.rs: `T::p(*b)` plus the prophecy link `!b == !x` between the -// loop's `&mut` and the entry `x`, with `x` rebound to `b` so both can be named. #[thrust_macros::context] #[thrust_macros::requires(T::p(*x) && n > 0)] #[thrust_macros::ensures(T::p(!x))] @@ -84,7 +81,6 @@ impl A for Y { fn target() -> (X, Y) { let (mut x, mut y) = (X(1), Y(-1)); repeat(&mut x, 3); - // `Y(-1)` does not satisfy `repeat`'s precondition `T::p(*x)`; `g` establishes it. y.g(); repeat(&mut y, 5); (x, y) diff --git a/tests/ui/pass/traits/simple_loop_self_mut.rs b/tests/ui/pass/traits/simple_loop_self_mut.rs index 4c2c0b0f..8dfd7f52 100644 --- a/tests/ui/pass/traits/simple_loop_self_mut.rs +++ b/tests/ui/pass/traits/simple_loop_self_mut.rs @@ -12,10 +12,6 @@ trait A { fn p(self, x: i64) -> bool; } -// The loop invariant is `T::p(*b, v)` plus the prophecy link `!b == !a` between -// the loop's `&mut` and the entry `a`; PCSat does not infer the latter, so it is -// spelled out. `a` is rebound to `b` because an `invariant!` cannot name both the -// current value and the `FnParam` entry value of the same `&mut` parameter. #[thrust_macros::context] #[thrust_macros::requires(T::p(*a, x))] #[thrust_macros::ensures(T::p(!a, result))] diff --git a/tests/ui/pass/traits/usize_counter.rs b/tests/ui/pass/traits/usize_counter.rs index 575b3b3c..9a6866b4 100644 --- a/tests/ui/pass/traits/usize_counter.rs +++ b/tests/ui/pass/traits/usize_counter.rs @@ -2,7 +2,6 @@ //@compile-flags: -C debug-assertions=off //@rustc-env: THRUST_SOLVER=tests/thrust-pcsat-wrapper THRUST_SOLVER_TIMEOUT_SECS=60 COAR_IMAGE=coar:latest -// Probe: a usize counter decremented behind &mut self (the Take counter pattern). struct C { n: usize, } diff --git a/tests/ui/pass/traits/wrap_delegate_mut.rs b/tests/ui/pass/traits/wrap_delegate_mut.rs index 10e3caa1..cc6f1d63 100644 --- a/tests/ui/pass/traits/wrap_delegate_mut.rs +++ b/tests/ui/pass/traits/wrap_delegate_mut.rs @@ -2,14 +2,12 @@ //@compile-flags: -C debug-assertions=off //@rustc-env: THRUST_SOLVER=tests/thrust-pcsat-wrapper THRUST_SOLVER_TIMEOUT_SECS=60 COAR_IMAGE=coar:latest -// Probe: `&mut self` prophecy flowing through a field of generic type (the id.rs pattern). #[thrust_macros::context] trait A { #[thrust_macros::requires(Self::p(*self))] #[thrust_macros::ensures(Self::p(!self))] fn f(&mut self); - // Same precondition, no postcondition: used by the `fail` twin. #[thrust_macros::requires(Self::p(*self))] fn g(&mut self); diff --git a/tests/ui/pass/traits/wrap_delegate_shared.rs b/tests/ui/pass/traits/wrap_delegate_shared.rs index a530edd1..f258e0c7 100644 --- a/tests/ui/pass/traits/wrap_delegate_shared.rs +++ b/tests/ui/pass/traits/wrap_delegate_shared.rs @@ -2,14 +2,12 @@ //@compile-flags: -C debug-assertions=off //@rustc-env: THRUST_SOLVER=tests/thrust-pcsat-wrapper THRUST_SOLVER_TIMEOUT_SECS=60 COAR_IMAGE=coar:latest -// Probe: delegation through a field of generic type, via a shared reference. #[thrust_macros::context] trait A { #[thrust_macros::requires(Self::p(*self, x))] #[thrust_macros::ensures(Self::p(*self, result))] fn f(&self, x: i64) -> i64; - // Same precondition, no postcondition: used by the `fail` twin. #[thrust_macros::requires(Self::p(*self, x))] fn g(&self, x: i64) -> i64; diff --git a/tests/ui/pass/traits/wrap_option_assoc.rs b/tests/ui/pass/traits/wrap_option_assoc.rs index 8b9c33dd..69fa1f1a 100644 --- a/tests/ui/pass/traits/wrap_option_assoc.rs +++ b/tests/ui/pass/traits/wrap_option_assoc.rs @@ -2,7 +2,6 @@ //@compile-flags: -C debug-assertions=off //@rustc-env: THRUST_SOLVER=tests/thrust-pcsat-wrapper THRUST_SOLVER_TIMEOUT_SECS=60 COAR_IMAGE=coar:latest -// Probe: Option flowing through a generic wrapper, rebuilt arm by arm. #[thrust_macros::context] trait A { type Item; @@ -10,7 +9,6 @@ trait A { #[thrust_macros::ensures(thrust_models::forall(|i| result == Some(i) ==> Self::ok(*self, i)))] fn get(&mut self) -> Option; - // No postcondition: used by the `fail` twin. fn other(&mut self) -> Option; #[thrust_macros::predicate] diff --git a/tests/ui/pass/traits/wrap_option_assoc_catchall.rs b/tests/ui/pass/traits/wrap_option_assoc_catchall.rs index 30b84fbc..ea040c5b 100644 --- a/tests/ui/pass/traits/wrap_option_assoc_catchall.rs +++ b/tests/ui/pass/traits/wrap_option_assoc_catchall.rs @@ -2,7 +2,6 @@ //@compile-flags: -C debug-assertions=off //@rustc-env: THRUST_SOLVER=tests/thrust-pcsat-wrapper THRUST_SOLVER_TIMEOUT_SECS=60 COAR_IMAGE=coar:latest -// Probe: Option flowing through a generic wrapper, via a catch-all arm (fuse.rs pattern). #[thrust_macros::context] trait A { type Item; @@ -10,7 +9,6 @@ trait A { #[thrust_macros::ensures(thrust_models::forall(|i| result == Some(i) ==> Self::ok(*self, i)))] fn get(&mut self) -> Option; - // No postcondition: used by the `fail` twin. fn other(&mut self) -> Option; #[thrust_macros::predicate] From af978e4f85d1d85e613f95ee6cccd2a77e5f14ee Mon Sep 17 00:00:00 2001 From: coeff-aij <175928954+coeff-aij@users.noreply.github.com> Date: Tue, 8 Sep 2026 22:24:55 +0900 Subject: [PATCH 128/142] Drop the annotation-error probe tests They were written to report macro and annotation typing problems, not to guard behaviour, and the problems they describe are settled. Co-Authored-By: Claude Opus 5 (1M context) --- .../ui/annot-error/array_index_literal_int.rs | 24 -------- .../array_index_literal_int_workaround.rs | 27 --------- .../annot-error/formula_fn_capture_local.rs | 47 --------------- .../invariant_context_self_trait_bound.rs | 50 ---------------- ...ant_context_self_trait_bound_workaround.rs | 60 ------------------- .../invariant_context_trait_method.rs | 27 --------- ...variant_context_trait_method_workaround.rs | 49 --------------- 7 files changed, 284 deletions(-) delete mode 100644 tests/ui/annot-error/array_index_literal_int.rs delete mode 100644 tests/ui/annot-error/array_index_literal_int_workaround.rs delete mode 100644 tests/ui/annot-error/formula_fn_capture_local.rs delete mode 100644 tests/ui/annot-error/invariant_context_self_trait_bound.rs delete mode 100644 tests/ui/annot-error/invariant_context_self_trait_bound_workaround.rs delete mode 100644 tests/ui/annot-error/invariant_context_trait_method.rs delete mode 100644 tests/ui/annot-error/invariant_context_trait_method_workaround.rs diff --git a/tests/ui/annot-error/array_index_literal_int.rs b/tests/ui/annot-error/array_index_literal_int.rs deleted file mode 100644 index bb64b171..00000000 --- a/tests/ui/annot-error/array_index_literal_int.rs +++ /dev/null @@ -1,24 +0,0 @@ -// Reproduces: an integer literal used as an `Array` index in a spec -// expression fails to type-check (E0308 "expected `Int`, found integer"). -// -// `thrust_models::model::Array` has an `Index` impl whose index -// type is the `I` parameter; for `Array` that is the `model::Int` -// ZST, which is not the same as Rust's `{integer}` literal type. The spec -// attribute path lowers `it[0]` as a Rust expression, so the `0` must be -// a `model::Int`-typed term. No such literal is constructible in Rust -// source today. -// -// See `array_index_literal_int_workaround.rs` for the bound-variable -// form that sidesteps the literal. - -#[thrust_macros::requires(true)] -#[thrust_macros::ensures( - thrust_models::exists(|it: thrust_models::model::Array| - it[0] == 0 - ) -)] -fn head(arr: Vec) -> i64 { - arr[0] -} - -fn main() {} diff --git a/tests/ui/annot-error/array_index_literal_int_workaround.rs b/tests/ui/annot-error/array_index_literal_int_workaround.rs deleted file mode 100644 index 74d74953..00000000 --- a/tests/ui/annot-error/array_index_literal_int_workaround.rs +++ /dev/null @@ -1,27 +0,0 @@ -// Annotation-side workaround for `array_index_literal_int.rs`. -// -// Rather than writing the literal `0` as the index (which fails because -// the `Index` impl on `Array` requires `I`-typed indices, and -// `model::Int` has no Rust literal form), bind the index with an -// existential and let typeck infer its sort: -// -// exists(|idx| it[idx] == 0) -// -// `idx` gets the `model::Int` sort from the `Index` site's expected -// `I = model::Int`. The expression type-checks; the trade-off is that -// the spec no longer pins a specific index like "0" or "1" — it just -// asserts "there exists some index such that the value at that index is 0". - -#[thrust_macros::requires(true)] -#[thrust_macros::ensures( - thrust_models::exists(|it: thrust_models::model::Array| - thrust_models::exists(|idx| - it[idx] == 0 - ) - ) -)] -fn head(arr: Vec) -> i64 { - arr[0] -} - -fn main() {} diff --git a/tests/ui/annot-error/formula_fn_capture_local.rs b/tests/ui/annot-error/formula_fn_capture_local.rs deleted file mode 100644 index ba7ec28c..00000000 --- a/tests/ui/annot-error/formula_fn_capture_local.rs +++ /dev/null @@ -1,47 +0,0 @@ -// Reproduces: a `formula_fn` produced by `requires`/`ensures`/`invariant!` -// cannot capture the surrounding function's local bindings (E0434 "can't -// capture dynamic environment in a fn item"). -// -// The macro lowers the spec into a free `fn _thrust_ensures_X(...)` whose -// only inputs are the host parameters (lowered to their `Model::Ty`) and -// the closure's bound variables. Any reference to a `let`-bound name in -// the host function is rejected. -// -// Same shape, applied to `_invariant_with_context!`: the host signature -// re-declared in the macro head (e.g. `fn run(self, f: B, g: F)`) -// is *not* threaded into the `formula_fn` parameters either; the macro -// currently only lowers the closure params plus the synthetic -// `__ThrustSelf` (for `Self` rewrite). -// -// No annotation-side workaround: this is a macro bug in -// `thrust-macros/src/invariant.rs::expand_invariant` (the host-signature -// re-declaration is parsed but its parameters are dropped on the floor). -// Either fix the macro to lower the re-declared signature's params via -// `type_lowering.lower_params(...)` and add them to the formula_fn, or -// restructure the invariant to not mention the host parameters (which -// often defeats the point of the invariant). - -#[thrust_macros::context] -trait Foo { - fn run(self, f: B, g: F) -> B - where - Self: Sized, - F: FnOnce(B) -> B, - { - let mut x: i64 = 0; - while x < 1 { - thrust_macros::_invariant_with_context!( - #[thrust::_outer_context(trait Foo {})] - fn run(self: Self, f: B, g: F) -> B - where - Self: Sized, - F: FnOnce(B) -> B; - |x: i64| x == f && g == f - ); - x += 1; - } - f - } -} - -fn main() {} diff --git a/tests/ui/annot-error/invariant_context_self_trait_bound.rs b/tests/ui/annot-error/invariant_context_self_trait_bound.rs deleted file mode 100644 index 25f9ca12..00000000 --- a/tests/ui/annot-error/invariant_context_self_trait_bound.rs +++ /dev/null @@ -1,50 +0,0 @@ -// Reproduces: when an `_invariant_with_context!` rewrites `Self` to a -// synthetic `__ThrustSelf` generic in the injected `formula_fn`, it does -// NOT automatically propagate the host trait bound (here `Self: Foo`). -// Calling trait items (the user-defined predicates `completed` / `step` -// and the associated `Item` type) on the synthetic `Self` therefore -// fails with E0599 / E0220 "no function / associated type named X found -// for `__ThrustSelf`". -// -// See `invariant_context_self_trait_bound_workaround.rs` for a partial -// workaround (`Self: Sized + Foo` in the re-declared where clause) that -// silences E0599 (the trait method calls) but leaves E0220 (the -// associated type) untouched — fully fixing the latter requires the -// `expand_invariant` macro to also rewrite `Self` to `__ThrustSelf` in -// the propagated where-clause predicates. - -#[thrust_macros::context] -trait Foo { - type Item; - - #[thrust_macros::predicate] - fn completed(self) -> bool; - #[thrust_macros::predicate] - fn step(self, item: Self::Item, dist: Self) -> bool; - - fn run(mut self, init: B, mut f: F) -> B - where - Self: Sized, - F: FnMut(B, Self::Item) -> B, - { - let mut accum = init; - while true { - thrust_macros::_invariant_with_context!( - #[thrust::_outer_context(trait Foo { type Item; })] - fn run(mut self: Self, init: B, mut f: F) -> B - where - Self: Sized, - F: FnMut(B, Self::Item) -> B; - |accum: B| thrust_models::exists( - |item: Self::Item| - Self::step(*self, item, *self) - && accum == init - ) - ); - break; - } - accum - } -} - -fn main() {} diff --git a/tests/ui/annot-error/invariant_context_self_trait_bound_workaround.rs b/tests/ui/annot-error/invariant_context_self_trait_bound_workaround.rs deleted file mode 100644 index 3fc07657..00000000 --- a/tests/ui/annot-error/invariant_context_self_trait_bound_workaround.rs +++ /dev/null @@ -1,60 +0,0 @@ -// Annotation-side partial workaround for -// `invariant_context_self_trait_bound.rs`. -// -// Adding the host trait bound to the re-declared signature's where -// clause (`Self: Sized + Foo` instead of just `Self: Sized`) makes the -// `expand_invariant` macro copy it into the `formula_fn`'s where -// clause, so `__ThrustSelf: Foo` is in scope. That silences the trait -// method-call errors (E0599 for `__ThrustSelf::step` / -// `__ThrustSelf::completed`). -// -// What it does NOT fix: E0220 for `__ThrustSelf::Item`. The macro's -// `where_predicates()` walk copies the re-declared where-clause -// predicates verbatim — `Self` is *not* rewritten to `__ThrustSelf` in -// the copied predicates, so the copy still talks about `Self` (host -// type) rather than `__ThrustSelf` (synthetic). The associated type -// `Item` lookup goes through `Self` instead of `__ThrustSelf`, and Rust -// complains. Fully fixing this needs the macro to rewrite `Self` to -// `__ThrustSelf` in the propagated where-clause predicates, then add -// `<__ThrustSelf as Foo>::Item` (or an analogous `Item` projection) to -// the `__ThrustSelf` parameter scope. - -#[thrust_macros::context] -trait Foo { - type Item; - - #[thrust_macros::predicate] - fn completed(self) -> bool; - #[thrust_macros::predicate] - fn step(self, item: Self::Item, dist: Self) -> bool; - - fn run(mut self, init: B, mut f: F) -> B - where - Self: Sized, - F: FnMut(B, Self::Item) -> B, - { - let mut accum = init; - while true { - thrust_macros::_invariant_with_context!( - #[thrust::_outer_context(trait Foo { type Item; })] - fn run(mut self: Self, init: B, mut f: F) -> B - where - // ← the partial-fix: add the host trait bound to - // the re-declared where clause. The macro copies - // it to the formula_fn where, so __ThrustSelf: Foo - // resolves the trait method calls. - Self: Sized + Foo, - F: FnMut(B, Self::Item) -> B; - |accum: B| thrust_models::exists( - |item: Self::Item| - Self::step(*self, item, *self) - && accum == init - ) - ); - break; - } - accum - } -} - -fn main() {} diff --git a/tests/ui/annot-error/invariant_context_trait_method.rs b/tests/ui/annot-error/invariant_context_trait_method.rs deleted file mode 100644 index d4bea507..00000000 --- a/tests/ui/annot-error/invariant_context_trait_method.rs +++ /dev/null @@ -1,27 +0,0 @@ -// Reproduces: `#[thrust_macros::invariant_context]` attached to a *trait* -// method fails with E0401 ("can't use `Self` from outer item"). -// -// `invariant_context` is `ItemFn`-only (`thrust-macros/src/invariant_context.rs`). -// On a trait method it parses, but it never threads the trait-level `Self` -// through to the generated `formula_fn`, so the injected -// `_invariant_with_context!` macro ends up rewriting the closure body against -// the outer trait's `Self` (which is out of scope) and Rust rejects the use. - -#[thrust_macros::context] -trait Foo { - type Item; - - #[thrust_macros::invariant_context] - fn run(&mut self) - where - Self: Sized, - { - let mut x: i64 = 0; - while x < 1 { - thrust_macros::invariant!(|x: i64| x >= 0); - x += 1; - } - } -} - -fn main() {} diff --git a/tests/ui/annot-error/invariant_context_trait_method_workaround.rs b/tests/ui/annot-error/invariant_context_trait_method_workaround.rs deleted file mode 100644 index 5630319d..00000000 --- a/tests/ui/annot-error/invariant_context_trait_method_workaround.rs +++ /dev/null @@ -1,49 +0,0 @@ -// Annotation-side workaround for `invariant_context_trait_method.rs`. -// -// The `#[thrust_macros::invariant_context]` attribute is `ItemFn`-only -// (its `expand` parses as `syn::ItemFn`), so attaching it to a trait -// method triggers E0401 because the trait's `Self` is out of scope for -// the generated `formula_fn`. The other annotation-side attempt — -// hand-rolling `thrust_macros::_invariant_with_context!` inside the -// loop body — runs into the same problem (the macro's `SelfRewriter` -// only kicks in when the closure body actually mentions `Self`; -// otherwise `Self: Model` constraints are produced against the outer -// `Self` and Rust rejects them). -// -// Workaround: drop the invariant on the trait method, and instead -// provide it on the concrete impl method, where `invariant_context` -// works (`ItemFn` parse target). The impl is the only place the -// invariant is meaningful anyway: the trait method's spec is -// independent of any concrete iterator type. - -#[thrust_macros::context] -trait Foo { - type Item; - - fn run(&mut self); -} - -struct Bar; - -impl thrust_models::Model for Bar { - type Ty = Bar; -} - -#[thrust_macros::context] -impl Foo for Bar { - type Item = i64; - - // `invariant_context` on an impl method is fine: the host is an - // `ItemFn` (`impl` method) and `Self` is the impl's self-type, - // not the trait's. - #[thrust_macros::invariant_context] - fn run(&mut self) { - let mut x: i64 = 0; - while x < 1 { - thrust_macros::invariant!(|x: i64| x >= 0); - x += 1; - } - } -} - -fn main() {} From e4acd6a07e5b3a8efcb84621c2c398380f04cb9e Mon Sep 17 00:00:00 2001 From: coeff-aij <175928954+coeff-aij@users.noreply.github.com> Date: Tue, 8 Sep 2026 22:51:02 +0900 Subject: [PATCH 129/142] Add the fold_noloop, id, and take adapter test pairs Each pass file verifies against the `Iterator` specification it declares, and its fail twin breaks only the property under test. Co-Authored-By: Claude Opus 5 (1M context) --- tests/ui/fail/traits/fold_noloop.rs | 59 +++++++++++++ tests/ui/fail/traits/fold_noloop_fn.rs | 58 +++++++++++++ tests/ui/fail/traits/id.rs | 79 ++++++++++++++++++ tests/ui/fail/traits/take.rs | 111 +++++++++++++++++++++++++ tests/ui/pass/traits/fold_noloop.rs | 58 +++++++++++++ tests/ui/pass/traits/fold_noloop_fn.rs | 57 +++++++++++++ tests/ui/pass/traits/id.rs | 78 +++++++++++++++++ tests/ui/pass/traits/take.rs | 111 +++++++++++++++++++++++++ 8 files changed, 611 insertions(+) create mode 100644 tests/ui/fail/traits/fold_noloop.rs create mode 100644 tests/ui/fail/traits/fold_noloop_fn.rs create mode 100644 tests/ui/fail/traits/id.rs create mode 100644 tests/ui/fail/traits/take.rs create mode 100644 tests/ui/pass/traits/fold_noloop.rs create mode 100644 tests/ui/pass/traits/fold_noloop_fn.rs create mode 100644 tests/ui/pass/traits/id.rs create mode 100644 tests/ui/pass/traits/take.rs diff --git a/tests/ui/fail/traits/fold_noloop.rs b/tests/ui/fail/traits/fold_noloop.rs new file mode 100644 index 00000000..8563b820 --- /dev/null +++ b/tests/ui/fail/traits/fold_noloop.rs @@ -0,0 +1,59 @@ +//@error-in-other-file: Unsat +//@compile-flags: -Aunused_mut -C debug-assertions=off +//@rustc-env: THRUST_SOLVER=tests/thrust-pcsat-wrapper THRUST_SOLVER_TIMEOUT_SECS=60 COAR_IMAGE=coar:latest + +use thrust_models::{exists, forall, Model, model::{Mut, Closure}}; + +#[thrust_macros::context] +trait Iterator { + type Item; + + #[thrust_macros::requires(Self::invariant(*self))] + #[thrust_macros::ensures(Self::invariant(!self))] + #[thrust_macros::ensures(result == None ==> Self::completed(self))] + #[thrust_macros::ensures(forall(|i| result == Some(i) ==> Self::step(*self, i, !self)))] + fn next(&mut self) -> Option; + + #[thrust_macros::predicate] + fn invariant(self) -> bool; + #[thrust_macros::predicate] + fn completed(&mut self) -> bool; + #[thrust_macros::predicate] + fn step(self, item: Self::Item, dist: Self) -> bool; + + + #[thrust_macros::requires( + Self::invariant(self) && + forall(|it: ::Ty| + forall(|item| + Self::step(self, item, it) + ==> forall(|f_final: Closure| thrust_macros::pre!(Mut::new(f, f_final)(init, item))) + )) + )] + #[thrust_macros::ensures( + exists(|it: ::Ty| + Self::completed(Mut::new(self, it)) && result == init + ) || + exists(|it: ::Ty| + exists(|item| + exists(|f_final: Closure| + Self::step(self, item, it) && + thrust_macros::pre!(Mut::new(f, f_final)(init, item)) && + thrust_macros::post!(Mut::new(f, f_final)(init, item), result) + ))) + )] + fn fold(mut self, init: B, mut f: F) -> B + where + Self: Sized, + F: FnMut(B, Self::Item) -> B, + { + let mut accum = init; + self.next(); + if let Some(x) = self.next() { + accum = f(accum, x); + } + accum + } +} + +fn main() {} diff --git a/tests/ui/fail/traits/fold_noloop_fn.rs b/tests/ui/fail/traits/fold_noloop_fn.rs new file mode 100644 index 00000000..922a4be9 --- /dev/null +++ b/tests/ui/fail/traits/fold_noloop_fn.rs @@ -0,0 +1,58 @@ +//@error-in-other-file: Unsat +//@compile-flags: -Aunused_mut -C debug-assertions=off +//@rustc-env: THRUST_SOLVER=tests/thrust-pcsat-wrapper THRUST_SOLVER_TIMEOUT_SECS=60 COAR_IMAGE=coar:latest + +use thrust_models::{exists, forall, Model, model::Mut}; + +#[thrust_macros::context] +trait Iterator { + type Item; + + #[thrust_macros::requires(Self::invariant(*self))] + #[thrust_macros::ensures(Self::invariant(!self))] + #[thrust_macros::ensures(result == None ==> Self::completed(self))] + #[thrust_macros::ensures(forall(|i| result == Some(i) ==> Self::step(*self, i, !self)))] + fn next(&mut self) -> Option; + + #[thrust_macros::predicate] + fn invariant(self) -> bool; + #[thrust_macros::predicate] + fn completed(&mut self) -> bool; + #[thrust_macros::predicate] + fn step(self, item: Self::Item, dist: Self) -> bool; + + + #[thrust_macros::requires( + Self::invariant(self) && + forall(|it: ::Ty| + forall(|item| + Self::step(self, item, it) + ==> thrust_macros::pre!(f(init, item)) + )) + )] + #[thrust_macros::ensures( + exists(|it: ::Ty| + Self::completed(Mut::new(self, it)) && result == init + ) || + exists(|it: ::Ty| + exists(|item| + Self::step(self, item, it) && + thrust_macros::pre!(f(init, item)) && + thrust_macros::post!(f(init, item), result) + )) + )] + fn fold(mut self, init: B, f: F) -> B + where + Self: Sized, + F: Fn(B, Self::Item) -> B, + { + let mut accum = init; + self.next(); + if let Some(x) = self.next() { + accum = f(accum, x); + } + accum + } +} + +fn main() {} diff --git a/tests/ui/fail/traits/id.rs b/tests/ui/fail/traits/id.rs new file mode 100644 index 00000000..61ccc062 --- /dev/null +++ b/tests/ui/fail/traits/id.rs @@ -0,0 +1,79 @@ +//@error-in-other-file: Unsat +//@compile-flags: -C debug-assertions=off +//@rustc-env: THRUST_SOLVER=tests/thrust-pcsat-wrapper COAR_IMAGE=coar:latest +use thrust_models::forall; + +#[thrust_macros::context] +trait Iterator { + type Item; + + #[thrust_macros::requires(Self::invariant(*self))] + #[thrust_macros::ensures(Self::invariant(!self))] + #[thrust_macros::ensures(result == None ==> Self::completed(self))] + #[thrust_macros::ensures(forall(|i| result == Some(i) ==> Self::step(*self, i, !self)))] + fn next(&mut self) -> Option; + + #[thrust_macros::predicate] + fn invariant(self) -> bool; + #[thrust_macros::predicate] + fn completed(&mut self) -> bool; + #[thrust_macros::predicate] + fn step(self, item: Self::Item, dist: Self) -> bool; +} + +pub struct Id { + iter: I, +} + +impl thrust_models::Model for Id { + type Ty = Id; +} + +#[thrust_macros::context] +impl Iterator for Id +where + I: Iterator + thrust_models::Model, + ::Item: thrust_models::Model, + ::Ty: PartialEq, +{ + type Item = I::Item; + + #[thrust_macros::predicate] + fn invariant(self) -> bool { + // self.iter.invariant() + "(q_invariant_c7a091bc1d03c6cf87779283240d85c2 (tuple_proj.0 self_))"; + true + } + + #[thrust_macros::predicate] + fn completed(&mut self) -> bool { + // self.iter.completed() + "(and + (q_completed_c7a091bc1d03c6cfc9dcf35ce8b9e5a + (mut + (tuple_proj.0 (mut_current> self_)) + (tuple_proj.0 (mut_final> self_)) + ) + ) + )"; + true + } + + #[thrust_macros::predicate] + fn step(self, item: Self::Item, dist: Self) -> bool { + // self.iter.step(item, dist.iter) + "(q_step_c7a091bc1d03c6cfc09f2d4a4c0c07ff + (tuple_proj.0 self_) + item + (tuple_proj.0 dist) + )"; + true + } + + fn next(&mut self) -> Option { + self.iter.next(); + None + } +} + +fn main() {} diff --git a/tests/ui/fail/traits/take.rs b/tests/ui/fail/traits/take.rs new file mode 100644 index 00000000..60bd02c2 --- /dev/null +++ b/tests/ui/fail/traits/take.rs @@ -0,0 +1,111 @@ +//@error-in-other-file: Unsat +//@rustc-env: THRUST_SOLVER=tests/thrust-pcsat-wrapper COAR_IMAGE=coar:latest +//@compile-flags: -C debug-assertions=off +use thrust_models::forall; + +#[thrust_macros::context] +trait Iterator { + type Item; + + #[thrust_macros::requires(Self::invariant(*self))] + #[thrust_macros::ensures(Self::invariant(!self))] + #[thrust_macros::ensures(result == None ==> Self::completed(self))] + #[thrust_macros::ensures(forall(|i| result == Some(i) ==> Self::step(*self, i, !self)))] + fn next(&mut self) -> Option; + + #[thrust_macros::predicate] + fn invariant(self) -> bool; + #[thrust_macros::predicate] + fn completed(&mut self) -> bool; + #[thrust_macros::predicate] + fn step(self, item: Self::Item, dist: Self) -> bool; +} + +pub struct Take { + iter: I, + n: usize, +} + +impl thrust_models::Model for Take { + type Ty = Take; +} + +#[thrust_macros::context] +impl Iterator for Take +where + I: Iterator + thrust_models::Model, + ::Item: thrust_models::Model, + ::Ty: PartialEq, +{ + type Item = I::Item; + + #[thrust_macros::predicate] + fn invariant(self) -> bool { + // self.invariant() && self.n >= 0 + "(and + (q_invariant_8cab213534b4e34b5a37430c4d78e732 (tuple_proj.0 self_)) + (>= (tuple_proj.1 self_) 0) + )"; + true + } + + #[thrust_macros::predicate] + fn completed(&mut self) -> bool { + // (*self.n == 0 && *self.iter == !self.iter && *self.n == !self.n) || + // (*self.iter.completed() && *self.n - 1 == !self.n) + "(or + (and + (= (tuple_proj.1 (mut_current> self_)) 0) + (= + (tuple_proj.0 (mut_current> self_)) + (tuple_proj.0 (mut_final> self_)) + ) + (= + (tuple_proj.1 (mut_current> self_)) + (tuple_proj.1 (mut_final> self_)) + ) + ) + (and + (q_completed_8cab213534b4e34b784965d8b6f1934e + (mut + (tuple_proj.0 (mut_current> self_)) + (tuple_proj.0 (mut_final> self_)) + ) + ) + (= + (- (tuple_proj.1 (mut_current> self_)) 1) + (tuple_proj.1 (mut_final> self_)) + ) + ) + )"; + true + } + + #[thrust_macros::predicate] + fn step(self, item: Self::Item, dist: Self) -> bool { + // self.iter.step(item, dist.iter) && dist.n == self.n - 1 + "(and + (q_step_8cab213534b4e34bf21e5845fb0de6dd + (tuple_proj.0 self_) + item + (tuple_proj.0 dist) + ) + (= + (tuple_proj.1 dist) + (- (tuple_proj.1 self_) 1) + ) + )"; + true + } + + fn next(&mut self) -> Option { + if self.n != 0 { + self.n -= 2; + self.iter.next() + } else { + None + } + } +} + +fn main() {} diff --git a/tests/ui/pass/traits/fold_noloop.rs b/tests/ui/pass/traits/fold_noloop.rs new file mode 100644 index 00000000..ce705856 --- /dev/null +++ b/tests/ui/pass/traits/fold_noloop.rs @@ -0,0 +1,58 @@ +//@check-pass +//@compile-flags: -Aunused_mut -C debug-assertions=off +//@rustc-env: THRUST_SOLVER=tests/thrust-pcsat-wrapper THRUST_SOLVER_TIMEOUT_SECS=60 COAR_IMAGE=coar:latest + +use thrust_models::{exists, forall, Model, model::{Mut, Closure}}; + +#[thrust_macros::context] +trait Iterator { + type Item; + + #[thrust_macros::requires(Self::invariant(*self))] + #[thrust_macros::ensures(Self::invariant(!self))] + #[thrust_macros::ensures(result == None ==> Self::completed(self))] + #[thrust_macros::ensures(forall(|i| result == Some(i) ==> Self::step(*self, i, !self)))] + fn next(&mut self) -> Option; + + #[thrust_macros::predicate] + fn invariant(self) -> bool; + #[thrust_macros::predicate] + fn completed(&mut self) -> bool; + #[thrust_macros::predicate] + fn step(self, item: Self::Item, dist: Self) -> bool; + + + #[thrust_macros::requires( + Self::invariant(self) && + forall(|it: ::Ty| + forall(|item| + Self::step(self, item, it) + ==> forall(|f_final: Closure| thrust_macros::pre!(Mut::new(f, f_final)(init, item))) + )) + )] + #[thrust_macros::ensures( + exists(|it: ::Ty| + Self::completed(Mut::new(self, it)) && result == init + ) || + exists(|it: ::Ty| + exists(|item| + exists(|f_final: Closure| + Self::step(self, item, it) && + thrust_macros::pre!(Mut::new(f, f_final)(init, item)) && + thrust_macros::post!(Mut::new(f, f_final)(init, item), result) + ))) + )] + fn fold(mut self, init: B, mut f: F) -> B + where + Self: Sized, + F: FnMut(B, Self::Item) -> B, + { + let mut accum = init; + if let Some(x) = self.next() { + accum = f(accum, x); + } + accum + } +} + +fn main() {} diff --git a/tests/ui/pass/traits/fold_noloop_fn.rs b/tests/ui/pass/traits/fold_noloop_fn.rs new file mode 100644 index 00000000..0f40dac8 --- /dev/null +++ b/tests/ui/pass/traits/fold_noloop_fn.rs @@ -0,0 +1,57 @@ +//@check-pass +//@compile-flags: -Aunused_mut -C debug-assertions=off +//@rustc-env: THRUST_SOLVER=tests/thrust-pcsat-wrapper THRUST_SOLVER_TIMEOUT_SECS=60 COAR_IMAGE=coar:latest + +use thrust_models::{exists, forall, Model, model::Mut}; + +#[thrust_macros::context] +trait Iterator { + type Item; + + #[thrust_macros::requires(Self::invariant(*self))] + #[thrust_macros::ensures(Self::invariant(!self))] + #[thrust_macros::ensures(result == None ==> Self::completed(self))] + #[thrust_macros::ensures(forall(|i| result == Some(i) ==> Self::step(*self, i, !self)))] + fn next(&mut self) -> Option; + + #[thrust_macros::predicate] + fn invariant(self) -> bool; + #[thrust_macros::predicate] + fn completed(&mut self) -> bool; + #[thrust_macros::predicate] + fn step(self, item: Self::Item, dist: Self) -> bool; + + + #[thrust_macros::requires( + Self::invariant(self) && + forall(|it: ::Ty| + forall(|item| + Self::step(self, item, it) + ==> thrust_macros::pre!(f(init, item)) + )) + )] + #[thrust_macros::ensures( + exists(|it: ::Ty| + Self::completed(Mut::new(self, it)) && result == init + ) || + exists(|it: ::Ty| + exists(|item| + Self::step(self, item, it) && + thrust_macros::pre!(f(init, item)) && + thrust_macros::post!(f(init, item), result) + )) + )] + fn fold(mut self, init: B, f: F) -> B + where + Self: Sized, + F: Fn(B, Self::Item) -> B, + { + let mut accum = init; + if let Some(x) = self.next() { + accum = f(accum, x); + } + accum + } +} + +fn main() {} diff --git a/tests/ui/pass/traits/id.rs b/tests/ui/pass/traits/id.rs new file mode 100644 index 00000000..07be2efd --- /dev/null +++ b/tests/ui/pass/traits/id.rs @@ -0,0 +1,78 @@ +//@check-pass +//@compile-flags: -C debug-assertions=off +//@rustc-env: THRUST_SOLVER=tests/thrust-pcsat-wrapper COAR_IMAGE=coar:latest +use thrust_models::forall; + +#[thrust_macros::context] +trait Iterator { + type Item; + + #[thrust_macros::requires(Self::invariant(*self))] + #[thrust_macros::ensures(Self::invariant(!self))] + #[thrust_macros::ensures(result == None ==> Self::completed(self))] + #[thrust_macros::ensures(forall(|i| result == Some(i) ==> Self::step(*self, i, !self)))] + fn next(&mut self) -> Option; + + #[thrust_macros::predicate] + fn invariant(self) -> bool; + #[thrust_macros::predicate] + fn completed(&mut self) -> bool; + #[thrust_macros::predicate] + fn step(self, item: Self::Item, dist: Self) -> bool; +} + +pub struct Id { + iter: I, +} + +impl thrust_models::Model for Id { + type Ty = Id; +} + +#[thrust_macros::context] +impl Iterator for Id +where + I: Iterator + thrust_models::Model, + ::Item: thrust_models::Model, + ::Ty: PartialEq, +{ + type Item = I::Item; + + #[thrust_macros::predicate] + fn invariant(self) -> bool { + // self.iter.invariant() + "(q_invariant_c7a091bc1d03c6cf87779283240d85c2 (tuple_proj.0 self_))"; + true + } + + #[thrust_macros::predicate] + fn completed(&mut self) -> bool { + // self.iter.completed() + "(and + (q_completed_c7a091bc1d03c6cfc9dcf35ce8b9e5a + (mut + (tuple_proj.0 (mut_current> self_)) + (tuple_proj.0 (mut_final> self_)) + ) + ) + )"; + true + } + + #[thrust_macros::predicate] + fn step(self, item: Self::Item, dist: Self) -> bool { + // self.iter.step(item, dist.iter) + "(q_step_c7a091bc1d03c6cfc09f2d4a4c0c07ff + (tuple_proj.0 self_) + item + (tuple_proj.0 dist) + )"; + true + } + + fn next(&mut self) -> Option { + self.iter.next() + } +} + +fn main() {} diff --git a/tests/ui/pass/traits/take.rs b/tests/ui/pass/traits/take.rs new file mode 100644 index 00000000..427acd3c --- /dev/null +++ b/tests/ui/pass/traits/take.rs @@ -0,0 +1,111 @@ +//@check-pass +//@rustc-env: THRUST_SOLVER=tests/thrust-pcsat-wrapper COAR_IMAGE=coar:latest +//@compile-flags: -C debug-assertions=off +use thrust_models::forall; + +#[thrust_macros::context] +trait Iterator { + type Item; + + #[thrust_macros::requires(Self::invariant(*self))] + #[thrust_macros::ensures(Self::invariant(!self))] + #[thrust_macros::ensures(result == None ==> Self::completed(self))] + #[thrust_macros::ensures(forall(|i| result == Some(i) ==> Self::step(*self, i, !self)))] + fn next(&mut self) -> Option; + + #[thrust_macros::predicate] + fn invariant(self) -> bool; + #[thrust_macros::predicate] + fn completed(&mut self) -> bool; + #[thrust_macros::predicate] + fn step(self, item: Self::Item, dist: Self) -> bool; +} + +pub struct Take { + iter: I, + n: usize, +} + +impl thrust_models::Model for Take { + type Ty = Take; +} + +#[thrust_macros::context] +impl Iterator for Take +where + I: Iterator + thrust_models::Model, + ::Item: thrust_models::Model, + ::Ty: PartialEq, +{ + type Item = I::Item; + + #[thrust_macros::predicate] + fn invariant(self) -> bool { + // self.invariant() && self.n >= 0 + "(and + (q_invariant_8cab213534b4e34b5a37430c4d78e732 (tuple_proj.0 self_)) + (>= (tuple_proj.1 self_) 0) + )"; + true + } + + #[thrust_macros::predicate] + fn completed(&mut self) -> bool { + // (*self.n == 0 && *self.iter == !self.iter && *self.n == !self.n) || + // (*self.iter.completed() && *self.n - 1 == !self.n) + "(or + (and + (= (tuple_proj.1 (mut_current> self_)) 0) + (= + (tuple_proj.0 (mut_current> self_)) + (tuple_proj.0 (mut_final> self_)) + ) + (= + (tuple_proj.1 (mut_current> self_)) + (tuple_proj.1 (mut_final> self_)) + ) + ) + (and + (q_completed_8cab213534b4e34b784965d8b6f1934e + (mut + (tuple_proj.0 (mut_current> self_)) + (tuple_proj.0 (mut_final> self_)) + ) + ) + (= + (- (tuple_proj.1 (mut_current> self_)) 1) + (tuple_proj.1 (mut_final> self_)) + ) + ) + )"; + true + } + + #[thrust_macros::predicate] + fn step(self, item: Self::Item, dist: Self) -> bool { + // self.iter.step(item, dist.iter) && dist.n == self.n - 1 + "(and + (q_step_8cab213534b4e34bf21e5845fb0de6dd + (tuple_proj.0 self_) + item + (tuple_proj.0 dist) + ) + (= + (tuple_proj.1 dist) + (- (tuple_proj.1 self_) 1) + ) + )"; + true + } + + fn next(&mut self) -> Option { + if self.n != 0 { + self.n -= 1; + self.iter.next() + } else { + None + } + } +} + +fn main() {} From 56219a23c7ecf08d16be6c59acf24a8e67afb741 Mon Sep 17 00:00:00 2001 From: coeff-aij <175928954+coeff-aij@users.noreply.github.com> Date: Tue, 8 Sep 2026 23:51:17 +0900 Subject: [PATCH 130/142] Lower `Ghost` through its content instead of trait normalization `Ghost` was known to the analyzer only through `impl Model for Ghost`, so `resolve_model_ty` had to normalize ` as Model>::Ty` to learn anything about it. In a function generic over `T` that normalization fails -- without a `T: Model` bound rustc cannot select the impl -- and the fallback hands back the unmodeled Rust type, a `PhantomData` newtype, which lowers to the singleton sort `(own (),)`. A parameter of singleton sort is not bound in `relate_sub_param_types`, while the refinement lifted from `#[requires(g == v)]` still names it, so building the entry obligation panicked with `unbound var $0`. Mark the struct and lower it structurally, the way `Closure` already is: in the logic a `Ghost` is its content, so `model_adt` returns the content type directly and no trait selection is involved. The `Model` impl stays, since a specification parameter still lowers to ` as Model>::Ty` and has to name it; a TODO on both sides records that the two have to agree. The `fail` twin still reports nothing: a generic function's parameter predicates never occur in a clause head, so its body is discharged vacuously and its callers constrain a separate pair -- the same gap the `fn_poly*` tests sit in. Co-Authored-By: Claude Opus 5 (1M context) --- src/analyze/annot.rs | 8 ++++++++ src/analyze/did_cache.rs | 8 ++++++++ src/refine/template.rs | 19 +++++++++++++++++++ std.rs | 8 ++++++++ tests/ui/fail/ghost_generic.rs | 1 + tests/ui/pass/ghost_generic.rs | 1 + 6 files changed, 45 insertions(+) diff --git a/src/analyze/annot.rs b/src/analyze/annot.rs index 390518dc..b92483cc 100644 --- a/src/analyze/annot.rs +++ b/src/analyze/annot.rs @@ -98,6 +98,14 @@ pub fn closure_model_path() -> [Symbol; 3] { ] } +pub fn ghost_model_path() -> [Symbol; 3] { + [ + Symbol::intern("thrust"), + Symbol::intern("def"), + Symbol::intern("ghost_model"), + ] +} + pub fn mut_model_new_path() -> [Symbol; 3] { [ Symbol::intern("thrust"), diff --git a/src/analyze/did_cache.rs b/src/analyze/did_cache.rs index da9c5e21..c2e5b8ad 100644 --- a/src/analyze/did_cache.rs +++ b/src/analyze/did_cache.rs @@ -19,6 +19,7 @@ struct DefIds { box_model: OnceCell>, array_model: OnceCell>, closure_model: OnceCell>, + ghost_model: OnceCell>, mut_model_new: OnceCell>, box_model_new: OnceCell>, @@ -170,6 +171,13 @@ impl<'tcx> DefIdCache<'tcx> { .get_or_init(|| self.annotated_def(&crate::analyze::annot::closure_model_path())) } + pub fn ghost_model(&self) -> Option { + *self + .def_ids + .ghost_model + .get_or_init(|| self.annotated_def(&crate::analyze::annot::ghost_model_path())) + } + pub fn mut_model_new(&self) -> Option { *self .def_ids diff --git a/src/refine/template.rs b/src/refine/template.rs index 41d2b4fb..22f6c86e 100644 --- a/src/refine/template.rs +++ b/src/refine/template.rs @@ -340,6 +340,18 @@ impl<'tcx> TypeBuilder<'tcx> { return Some(self.build(tupled_upvars_ty)); } + // TODO: keep this in step with `impl Model for Ghost` in std.rs, which + // resolves a `Ghost` to its content as well. + // + // Which of the two applies is not evident from the source: a concrete `Ghost` + // normalizes and never reaches here, while a generic one does, because + // `resolve_model_ty` either fails to normalize it or discards the partly normalized + // result. See that impl for why it cannot be a fixed point like the models above. + if Some(adt.did()) == self.def_ids.ghost_model() { + let content_ty = args.type_at(0); + return Some(self.build(content_ty)); + } + None } @@ -728,6 +740,13 @@ where return Some(self.build(tupled_upvars_ty)); } + // TODO: keep in step with `impl Model for Ghost` in std.rs; see + // `TypeBuilder::model_adt`. + if Some(adt.did()) == self.inner.def_ids.ghost_model() { + let content_ty = args.type_at(0); + return Some(self.build(content_ty)); + } + None } diff --git a/std.rs b/std.rs index 96d88669..f29645aa 100644 --- a/std.rs +++ b/std.rs @@ -415,6 +415,7 @@ mod thrust_models { /// Proof-only data, introduced by `thrust_macros::ghost!`. In the logic it is its /// content, so a specification refers to a `Ghost` as if it were a `T`. #[allow(dead_code)] + #[thrust::def::ghost_model] pub struct Ghost(std::marker::PhantomData); impl Clone for Ghost { @@ -426,6 +427,13 @@ mod thrust_models { impl Copy for Ghost {} + // TODO: keep this in step with the `ghost_model` arm of `model_adt` in + // `refine::template`, which resolves a `Ghost` to its content as well. + // + // The other `#[thrust::def::*_model]` types are fixed points of `Model` and leave the + // meaning to `model_adt` alone. This one cannot be: a specification names a ghost value + // as its content (`s.len()` on a `Ghost>`), so the lifted formula function has + // to receive `::Ty` for the term to type-check. impl Model for Ghost where T: Model { type Ty = ::Ty; } diff --git a/tests/ui/fail/ghost_generic.rs b/tests/ui/fail/ghost_generic.rs index 5dc8c4d9..660a3095 100644 --- a/tests/ui/fail/ghost_generic.rs +++ b/tests/ui/fail/ghost_generic.rs @@ -1,5 +1,6 @@ //@error-in-other-file: Unsat //@compile-flags: -C debug-assertions=off -A unused-variables +//@rustc-env: THRUST_SOLVER=tests/thrust-pcsat-wrapper COAR_IMAGE=coar:latest use thrust_models::Ghost; diff --git a/tests/ui/pass/ghost_generic.rs b/tests/ui/pass/ghost_generic.rs index 9962c703..b547732f 100644 --- a/tests/ui/pass/ghost_generic.rs +++ b/tests/ui/pass/ghost_generic.rs @@ -1,5 +1,6 @@ //@check-pass //@compile-flags: -C debug-assertions=off -A unused-variables +//@rustc-env: THRUST_SOLVER=tests/thrust-pcsat-wrapper COAR_IMAGE=coar:latest use thrust_models::Ghost; From 1e4deb1fd7752e4144aae43b5c034a5c316ed416 Mon Sep 17 00:00:00 2001 From: coeff-aij <175928954+coeff-aij@users.noreply.github.com> Date: Tue, 8 Sep 2026 23:45:03 +0900 Subject: [PATCH 131/142] Add the annotated variants of the two-loop trait tests two_loops.rs and multi_params.rs time out while the loop invariants are being inferred; these variants supply them by hand, as annot_simple_loop_self.rs does for simple_loop_self.rs. Co-Authored-By: Claude Opus 5 (1M context) --- tests/ui/fail/traits/annot_multi_params.rs | 44 ++++++++++++++++++++++ tests/ui/fail/traits/annot_two_loops.rs | 43 +++++++++++++++++++++ tests/ui/pass/traits/annot_multi_params.rs | 44 ++++++++++++++++++++++ tests/ui/pass/traits/annot_two_loops.rs | 43 +++++++++++++++++++++ 4 files changed, 174 insertions(+) create mode 100644 tests/ui/fail/traits/annot_multi_params.rs create mode 100644 tests/ui/fail/traits/annot_two_loops.rs create mode 100644 tests/ui/pass/traits/annot_multi_params.rs create mode 100644 tests/ui/pass/traits/annot_two_loops.rs diff --git a/tests/ui/fail/traits/annot_multi_params.rs b/tests/ui/fail/traits/annot_multi_params.rs new file mode 100644 index 00000000..ddb83c99 --- /dev/null +++ b/tests/ui/fail/traits/annot_multi_params.rs @@ -0,0 +1,44 @@ +//@error-in-other-file: Unsat +//@compile-flags: -C debug-assertions=off +//@rustc-env: THRUST_SOLVER=tests/thrust-pcsat-wrapper THRUST_SOLVER_TIMEOUT_SECS=60 COAR_IMAGE=coar:latest + +#[thrust_macros::context] +trait A { + #[thrust_macros::requires(Self::p(*self))] + #[thrust_macros::ensures(Self::p(!self))] + fn f(&mut self); + #[thrust_macros::requires(true)] + #[thrust_macros::ensures(Self::p(!self))] + fn g(&mut self); + + #[thrust_macros::predicate] + fn p(self) -> bool; +} + +#[thrust_macros::context] +#[thrust_macros::requires(n > 0)] +#[thrust_macros::ensures(T::p(!x) && S::p(!y))] +fn multi_loop<'a, T: A, S: A>(x: &mut T, y: &mut S, n: u64) { + let a = x; + let b = y; + + let mut i = 0; + while i < n { // The loop depends on P + thrust_macros::invariant!( + |a: &mut T, b: &mut S, x: thrust_models::FnParam<&mut T>, y: thrust_models::FnParam<&mut S>, n: u64| + n > 0 && T::p(*a) && !a == !x.at_entry() && !b == !y.at_entry() + ); + a.f(); i += 1; + } + + let mut j = 0; + while j < n { // The loop depends on Q + thrust_macros::invariant!( + |b: &mut S, x: thrust_models::FnParam<&mut T>, y: thrust_models::FnParam<&mut S>, j: u64, n: u64| + n > 0 && T::p(!x.at_entry()) && (j > 0 ==> S::p(*b)) && !b == !y.at_entry() + ); + b.g(); j += 1; + } +} + +fn main() {} diff --git a/tests/ui/fail/traits/annot_two_loops.rs b/tests/ui/fail/traits/annot_two_loops.rs new file mode 100644 index 00000000..139d3882 --- /dev/null +++ b/tests/ui/fail/traits/annot_two_loops.rs @@ -0,0 +1,43 @@ +//@error-in-other-file: Unsat +//@compile-flags: -C debug-assertions=off +//@rustc-env: THRUST_SOLVER=tests/thrust-pcsat-wrapper THRUST_SOLVER_TIMEOUT_SECS=60 COAR_IMAGE=coar:latest + +#[thrust_macros::context] +trait A { + #[thrust_macros::requires(true)] + #[thrust_macros::ensures(Self::p(*result))] + fn f(&self) -> &Self; + #[thrust_macros::requires(Self::q(*self))] + #[thrust_macros::ensures(Self::q(*result))] + fn g(&self) -> &Self; + + #[thrust_macros::predicate] + fn p(self) -> bool; + #[thrust_macros::predicate] + fn q(self) -> bool; +} + +#[thrust_macros::context] +#[thrust_macros::requires(T::q(*y))] +#[thrust_macros::ensures(T::p(*result.0) && T::q(*result.1))] +fn target<'a, T: A>(x: &'a T, y: &'a T) -> (&'a T, &'a T) { + let mut v = x; + let mut w = y; + let mut i = 0; + while i < 3 { // The loop depends on P + thrust_macros::invariant!(|v: &T, w: &T, i: i64| (i > 0 ==> T::p(*v)) && T::q(*w)); + v = v.f(); + i += 1; + } + + let mut j = 0; + while j < 3 { // The loop depends on Q + thrust_macros::invariant!(|v: &T, w: &T| T::q(*w)); + w = w.g(); + j += 1; + } + + (v, w) +} + +fn main() {} diff --git a/tests/ui/pass/traits/annot_multi_params.rs b/tests/ui/pass/traits/annot_multi_params.rs new file mode 100644 index 00000000..78b4f0be --- /dev/null +++ b/tests/ui/pass/traits/annot_multi_params.rs @@ -0,0 +1,44 @@ +//@check-pass +//@compile-flags: -C debug-assertions=off +//@rustc-env: THRUST_SOLVER=tests/thrust-pcsat-wrapper THRUST_SOLVER_TIMEOUT_SECS=60 COAR_IMAGE=coar:latest + +#[thrust_macros::context] +trait A { + #[thrust_macros::requires(Self::p(*self))] + #[thrust_macros::ensures(Self::p(!self))] + fn f(&mut self); + #[thrust_macros::requires(true)] + #[thrust_macros::ensures(Self::p(!self))] + fn g(&mut self); + + #[thrust_macros::predicate] + fn p(self) -> bool; +} + +#[thrust_macros::context] +#[thrust_macros::requires(T::p(*x) && n > 0)] +#[thrust_macros::ensures(T::p(!x) && S::p(!y))] +fn multi_loop<'a, T: A, S: A>(x: &mut T, y: &mut S, n: u64) { + let a = x; + let b = y; + + let mut i = 0; + while i < n { // The loop depends on P + thrust_macros::invariant!( + |a: &mut T, b: &mut S, x: thrust_models::FnParam<&mut T>, y: thrust_models::FnParam<&mut S>, n: u64| + n > 0 && T::p(*a) && !a == !x.at_entry() && !b == !y.at_entry() + ); + a.f(); i += 1; + } + + let mut j = 0; + while j < n { // The loop depends on Q + thrust_macros::invariant!( + |b: &mut S, x: thrust_models::FnParam<&mut T>, y: thrust_models::FnParam<&mut S>, j: u64, n: u64| + n > 0 && T::p(!x.at_entry()) && (j > 0 ==> S::p(*b)) && !b == !y.at_entry() + ); + b.g(); j += 1; + } +} + +fn main() {} diff --git a/tests/ui/pass/traits/annot_two_loops.rs b/tests/ui/pass/traits/annot_two_loops.rs new file mode 100644 index 00000000..0264c63a --- /dev/null +++ b/tests/ui/pass/traits/annot_two_loops.rs @@ -0,0 +1,43 @@ +//@check-pass +//@compile-flags: -C debug-assertions=off +//@rustc-env: THRUST_SOLVER=tests/thrust-pcsat-wrapper THRUST_SOLVER_TIMEOUT_SECS=60 COAR_IMAGE=coar:latest + +#[thrust_macros::context] +trait A { + #[thrust_macros::requires(true)] + #[thrust_macros::ensures(Self::p(*result))] + fn f(&self) -> &Self; + #[thrust_macros::requires(Self::q(*self))] + #[thrust_macros::ensures(Self::q(*result))] + fn g(&self) -> &Self; + + #[thrust_macros::predicate] + fn p(self) -> bool; + #[thrust_macros::predicate] + fn q(self) -> bool; +} + +#[thrust_macros::context] +#[thrust_macros::requires(T::q(*y))] +#[thrust_macros::ensures(T::p(*result.0) && T::q(*result.1))] +fn target<'a, T: A>(x: &'a T, y: &'a T) -> (&'a T, &'a T) { + let mut v = x; + let mut w = y; + let mut i = 0; + while i < 3 { // The loop depends on P + thrust_macros::invariant!(|v: &T, w: &T, i: i64| (i > 0 ==> T::p(*v)) && T::q(*w)); + v = v.f(); + i += 1; + } + + let mut j = 0; + while j < 3 { // The loop depends on Q + thrust_macros::invariant!(|v: &T, w: &T| T::p(*v) && T::q(*w)); + w = w.g(); + j += 1; + } + + (v, w) +} + +fn main() {} From 26bf976db9c55ce160c51878da7d79372326442e Mon Sep 17 00:00:00 2001 From: coeff-aij <175928954+coeff-aij@users.noreply.github.com> Date: Wed, 9 Sep 2026 00:26:52 +0900 Subject: [PATCH 132/142] Add the annotated variants of the fixed-filter tests The inference versions time out; these supply the inner loop's invariant by hand, as annot_simple_loop_self.rs does for simple_loop_self.rs. Co-Authored-By: Claude Opus 5 (1M context) --- .../iterators/annot_fixed_filter_loop_none.rs | 64 ++++++++++++++++++ .../iterators/annot_fixed_filter_next_some.rs | 56 ++++++++++++++++ .../iterators/annot_fixed_filter_loop_none.rs | 65 +++++++++++++++++++ .../iterators/annot_fixed_filter_next_some.rs | 56 ++++++++++++++++ 4 files changed, 241 insertions(+) create mode 100644 tests/ui/fail/iterators/annot_fixed_filter_loop_none.rs create mode 100644 tests/ui/fail/iterators/annot_fixed_filter_next_some.rs create mode 100644 tests/ui/pass/iterators/annot_fixed_filter_loop_none.rs create mode 100644 tests/ui/pass/iterators/annot_fixed_filter_next_some.rs diff --git a/tests/ui/fail/iterators/annot_fixed_filter_loop_none.rs b/tests/ui/fail/iterators/annot_fixed_filter_loop_none.rs new file mode 100644 index 00000000..20f8b349 --- /dev/null +++ b/tests/ui/fail/iterators/annot_fixed_filter_loop_none.rs @@ -0,0 +1,64 @@ +//@error-in-other-file: Unsat +//@compile-flags: -C debug-assertions=off +//@rustc-env: THRUST_SOLVER=tests/thrust-pcsat-wrapper COAR_IMAGE=coar:latest + +struct Range { + start: i64, + end: i64, +} + +impl thrust_models::Model for Range { + type Ty = Range; +} + +impl Iterator for Range { + type Item = i64; + + fn next(&mut self) -> Option { + if self.start < self.end { + let item = self.start; + self.start += 1; + Some(item) + } else { + None + } + } +} + +struct FixedFilter { + iter: Range, +} + +impl thrust_models::Model for FixedFilter { + type Ty = FixedFilter; +} + +impl Iterator for FixedFilter { + type Item = ::Item; + + fn next(&mut self) -> Option { + let it = &mut self.iter; + while let Some(item) = it.next() { + thrust_macros::invariant!(|it: &mut Range| (*it).end <= 10); + if item >= 10 { + return Some(item); + } + } + None + } +} + +fn main() { + let range = Range { start: 0, end: 5 }; + + let mut adapter = FixedFilter { iter: range }; + + let mut count = 0; + let mut last = None; + while let Some(i) = adapter.next() { + count += 1; + last = Some(i); + } + + assert!(count > 0); +} diff --git a/tests/ui/fail/iterators/annot_fixed_filter_next_some.rs b/tests/ui/fail/iterators/annot_fixed_filter_next_some.rs new file mode 100644 index 00000000..c60418da --- /dev/null +++ b/tests/ui/fail/iterators/annot_fixed_filter_next_some.rs @@ -0,0 +1,56 @@ +//@error-in-other-file: Unsat +//@compile-flags: -C debug-assertions=off +//@rustc-env: THRUST_SOLVER=tests/thrust-pcsat-wrapper COAR_IMAGE=coar:latest + +struct Range { + start: i64, + end: i64, +} + +impl thrust_models::Model for Range { + type Ty = Range; +} + +impl Iterator for Range { + type Item = i64; + + fn next(&mut self) -> Option { + if self.start < self.end { + let item = self.start; + self.start += 1; + Some(item) + } else { + None + } + } +} + +struct FixedFilter { + iter: Range, +} + +impl thrust_models::Model for FixedFilter { + type Ty = FixedFilter; +} + +impl Iterator for FixedFilter { + type Item = ::Item; + + fn next(&mut self) -> Option { + let it = &mut self.iter; + while let Some(item) = it.next() { + thrust_macros::invariant!(|it: &mut Range| (*it).end >= 3 && (*it).start <= 2); + if item >= 2 { + return Some(item); + } + } + None + } +} + +fn main() { + let range = Range { start: 0, end: 5 }; + let mut adapter = FixedFilter { iter: range }; + + assert!(matches!(adapter.next(), Some(0))); +} diff --git a/tests/ui/pass/iterators/annot_fixed_filter_loop_none.rs b/tests/ui/pass/iterators/annot_fixed_filter_loop_none.rs new file mode 100644 index 00000000..87ae393f --- /dev/null +++ b/tests/ui/pass/iterators/annot_fixed_filter_loop_none.rs @@ -0,0 +1,65 @@ +//@check-pass +//@compile-flags: -C debug-assertions=off +//@rustc-env: THRUST_SOLVER=tests/thrust-pcsat-wrapper COAR_IMAGE=coar:latest + +struct Range { + start: i64, + end: i64, +} + +impl thrust_models::Model for Range { + type Ty = Range; +} + +impl Iterator for Range { + type Item = i64; + + fn next(&mut self) -> Option { + if self.start < self.end { + let item = self.start; + self.start += 1; + Some(item) + } else { + None + } + } +} + +struct FixedFilter { + iter: Range, +} + +impl thrust_models::Model for FixedFilter { + type Ty = FixedFilter; +} + +impl Iterator for FixedFilter { + type Item = ::Item; + + fn next(&mut self) -> Option { + let it = &mut self.iter; + while let Some(item) = it.next() { + thrust_macros::invariant!(|it: &mut Range| (*it).end <= 10); + if item >= 10 { + return Some(item); + } + } + None + } +} + +fn main() { + let range = Range { start: 0, end: 5 }; + + let mut adapter = FixedFilter { iter: range }; + + let mut count = 0; + let mut last = None; + while let Some(i) = adapter.next() { + count += 1; + last = Some(i); + } + + assert!(count == 0); + assert!(matches!(last, None)); +} diff --git a/tests/ui/pass/iterators/annot_fixed_filter_next_some.rs b/tests/ui/pass/iterators/annot_fixed_filter_next_some.rs new file mode 100644 index 00000000..702cb3e0 --- /dev/null +++ b/tests/ui/pass/iterators/annot_fixed_filter_next_some.rs @@ -0,0 +1,56 @@ +//@check-pass +//@compile-flags: -C debug-assertions=off +//@rustc-env: THRUST_SOLVER=tests/thrust-pcsat-wrapper COAR_IMAGE=coar:latest + +struct Range { + start: i64, + end: i64, +} + +impl thrust_models::Model for Range { + type Ty = Range; +} + +impl Iterator for Range { + type Item = i64; + + fn next(&mut self) -> Option { + if self.start < self.end { + let item = self.start; + self.start += 1; + Some(item) + } else { + None + } + } +} + +struct FixedFilter { + iter: Range, +} + +impl thrust_models::Model for FixedFilter { + type Ty = FixedFilter; +} + +impl Iterator for FixedFilter { + type Item = ::Item; + + fn next(&mut self) -> Option { + let it = &mut self.iter; + while let Some(item) = it.next() { + thrust_macros::invariant!(|it: &mut Range| (*it).end >= 3 && (*it).start <= 2); + if item >= 2 { + return Some(item); + } + } + None + } +} + +fn main() { + let range = Range { start: 0, end: 5 }; + let mut adapter = FixedFilter { iter: range }; + + assert!(matches!(adapter.next(), Some(2))); +} From d4ddb53f2dfc15f2f9ee1154491b89bb6ff054a8 Mon Sep 17 00:00:00 2001 From: coeff-aij <175928954+coeff-aij@users.noreply.github.com> Date: Mon, 31 Aug 2026 22:55:00 +0900 Subject: [PATCH 133/142] fix: track generic fn call results by re-analyzing bodies at concrete args Generic function calls at concrete type arguments built a fresh function type whose predicate variables were never constrained: the body was analyzed once, with placeholder args, constraining a different set of predicate variables. Call sites therefore learned nothing about the callee, so assertions on returned values were left unchecked (unsound). Re-analyze the monomorphized body at each concrete instantiation (DefTy::Generic now uses DeferredDefMode::Analyze) so the fresh contract predicate variables are constrained by the body. Basic-block types are registered per analysis instance (AnalysisKey), so nested analyses of the same def (e.g. recursive generics) no longer clobber each other, and calls still carrying type parameters keep using the placeholder contract. This fixes the known-bug in adt_generic_enum_helper_return and makes 18 previously silently-accepted fail tests report Unsat. --- src/analyze.rs | 79 +++++++++++++++++++++++++++++--------- src/analyze/basic_block.rs | 13 ++++--- src/analyze/local_def.rs | 16 +++++--- 3 files changed, 79 insertions(+), 29 deletions(-) diff --git a/src/analyze.rs b/src/analyze.rs index 63b935d6..c8a4f761 100644 --- a/src/analyze.rs +++ b/src/analyze.rs @@ -194,6 +194,34 @@ struct InstantiationKey<'tcx> { caller_def_id: DefId, } +/// Identifies one analysis instance of a function body. +/// +/// A def may be analyzed more than once: the placeholder analysis (with the +/// type parameters left as forall sorts) and, when a generic def is called at +/// concrete type arguments, one analysis per instantiation. Each instance owns +/// its own basic-block types so that nested analyses of the same def (e.g. a +/// recursive generic function) do not clobber each other. +#[derive(Debug, Clone, Copy, Hash, PartialEq, Eq)] +pub struct AnalysisKey<'tcx> { + local_def_id: LocalDefId, + generic_args: mir_ty::GenericArgsRef<'tcx>, + owner_fn_id: DefId, +} + +impl<'tcx> AnalysisKey<'tcx> { + pub fn new( + local_def_id: LocalDefId, + generic_args: mir_ty::GenericArgsRef<'tcx>, + owner_fn_id: DefId, + ) -> Self { + Self { + local_def_id, + generic_args, + owner_fn_id, + } + } +} + #[derive(Debug, Clone)] enum DefTy<'tcx> { Concrete(rty::RefinedType), @@ -270,7 +298,7 @@ pub struct Analyzer<'tcx> { /// Resulting CHC system. system: Rc>, - basic_blocks: HashMap>, + basic_blocks: HashMap, HashMap>, def_ids: did_cache::DefIdCache<'tcx>, enum_defs: Rc>, @@ -572,7 +600,18 @@ impl<'tcx> Analyzer<'tcx> { Self::instantiate_generic_args(&mut def_ty, generic_args, &type_builder); return Some(def_ty); } - DefTy::Generic(generic) => (generic.local_def_id, Rc::clone(&generic.cache), None), + DefTy::Generic(generic) => ( + generic.local_def_id, + Rc::clone(&generic.cache), + Some(DeferredDefMode::Analyze).filter(|_| { + // A call with type parameters still present is a call from + // inside a generic context; its contract is the template that + // the placeholder analysis constrains. Re-running the body here + // would collide with that analysis (same def, same args). + use mir_ty::TypeVisitableExt as _; + !generic_args.types().any(|ty| ty.has_param()) + }), + ), DefTy::Deferred(deferred) => ( deferred.local_def_id, Rc::clone(&deferred.cache), @@ -627,12 +666,12 @@ impl<'tcx> Analyzer<'tcx> { pub fn register_basic_block_ty_with_precondition( &mut self, - def_id: LocalDefId, + key: AnalysisKey<'tcx>, bb: BasicBlock, rty: BasicBlockType, ) { self.register_basic_block_def( - def_id, + key, bb, BasicBlockDef { ty: rty, @@ -643,12 +682,12 @@ impl<'tcx> Analyzer<'tcx> { pub fn register_basic_block_ty_without_precondition( &mut self, - def_id: LocalDefId, + key: AnalysisKey<'tcx>, bb: BasicBlock, rty: BasicBlockType, ) { self.register_basic_block_def( - def_id, + key, bb, BasicBlockDef { ty: rty, @@ -657,26 +696,31 @@ impl<'tcx> Analyzer<'tcx> { ); } - fn register_basic_block_def(&mut self, def_id: LocalDefId, bb: BasicBlock, def: BasicBlockDef) { + fn register_basic_block_def( + &mut self, + key: AnalysisKey<'tcx>, + bb: BasicBlock, + def: BasicBlockDef, + ) { tracing::debug!( - def_id = ?def_id, + def_id = ?key.local_def_id, ?bb, rty = %def.ty.display(), has_precondition = def.has_precondition, "register_basic_block_def", ); - self.basic_blocks.entry(def_id).or_default().insert(bb, def); + self.basic_blocks.entry(key).or_default().insert(bb, def); } pub fn register_basic_block_precondition( &mut self, - def_id: LocalDefId, + key: AnalysisKey<'tcx>, bb: BasicBlock, precondition: rty::Refinement, ) { let bb_def = &mut self .basic_blocks - .get_mut(&def_id) + .get_mut(&key) .unwrap() .get_mut(&bb) .unwrap(); @@ -688,16 +732,16 @@ impl<'tcx> Analyzer<'tcx> { bb_def.ty.set_precondition(precondition); } - pub fn basic_block_ty(&self, def_id: LocalDefId, bb: BasicBlock) -> &BasicBlockType { - &self.basic_blocks[&def_id][&bb].ty + pub fn basic_block_ty(&self, key: AnalysisKey<'tcx>, bb: BasicBlock) -> &BasicBlockType { + &self.basic_blocks[&key][&bb].ty } pub fn basic_block_ty_with_precondition( &self, - def_id: LocalDefId, + key: AnalysisKey<'tcx>, bb: BasicBlock, ) -> &BasicBlockType { - let def = &self.basic_blocks[&def_id][&bb]; + let def = &self.basic_blocks[&key][&bb]; assert!( def.has_precondition, "basic block does not have precondition" @@ -737,11 +781,10 @@ impl<'tcx> Analyzer<'tcx> { pub fn basic_block_analyzer( &mut self, - local_def_id: LocalDefId, + key: AnalysisKey<'tcx>, bb: BasicBlock, - owner_fn_id: DefId, ) -> basic_block::Analyzer<'tcx, '_> { - basic_block::Analyzer::new(self, local_def_id, bb, owner_fn_id) + basic_block::Analyzer::new(self, key, bb) } pub fn type_builder(&self, def_ids: DefIdCache<'tcx>, owner_fn_id: DefId) -> TypeBuilder<'tcx> { diff --git a/src/analyze/basic_block.rs b/src/analyze/basic_block.rs index 80b63762..bfb38db2 100644 --- a/src/analyze/basic_block.rs +++ b/src/analyze/basic_block.rs @@ -141,6 +141,7 @@ pub struct Analyzer<'tcx, 'ctx> { tcx: TyCtxt<'tcx>, local_def_id: LocalDefId, + analysis_key: analyze::AnalysisKey<'tcx>, drop_points: DropPoints, basic_block: BasicBlock, body: Cow<'tcx, Body<'tcx>>, @@ -175,7 +176,7 @@ impl<'tcx, 'ctx> Analyzer<'tcx, 'ctx> { fn basic_block_ty_with_precondition(&self, bb: BasicBlock) -> &BasicBlockType { self.ctx - .basic_block_ty_with_precondition(self.local_def_id, bb) + .basic_block_ty_with_precondition(self.analysis_key, bb) } fn bind_local(&mut self, local: Local, rty: rty::RefinedType) { @@ -799,7 +800,7 @@ impl<'tcx, 'ctx> Analyzer<'tcx, 'ctx> { bb: BasicBlock, outer_fn_param_vars: &HashMap, ) { - let bty = self.ctx.basic_block_ty(self.local_def_id, bb); + let bty = self.ctx.basic_block_ty(self.analysis_key, bb); let mut capture = PrecondCapture::default(); for (param_idx, param_rty) in bty.as_ref().params.iter_enumerated() { @@ -823,7 +824,7 @@ impl<'tcx, 'ctx> Analyzer<'tcx, 'ctx> { let precondition = capture.finish(&self.env); self.ctx - .register_basic_block_precondition(self.local_def_id, bb, precondition); + .register_basic_block_precondition(self.analysis_key, bb, precondition); } fn with_assumptions(&mut self, assumptions: Vec>, callback: F) -> T @@ -1635,10 +1636,11 @@ impl<'tcx, 'ctx> Analyzer<'tcx, 'ctx> { impl<'tcx, 'ctx> Analyzer<'tcx, 'ctx> { pub fn new( ctx: &'ctx mut analyze::Analyzer<'tcx>, - local_def_id: LocalDefId, + analysis_key: analyze::AnalysisKey<'tcx>, basic_block: BasicBlock, - owner_fn_id: DefId, ) -> Self { + let local_def_id = analysis_key.local_def_id; + let owner_fn_id = analysis_key.owner_fn_id; let tcx = ctx.tcx; let drop_points = DropPoints::default(); let body = Cow::Borrowed(tcx.optimized_mir(local_def_id.to_def_id())); @@ -1650,6 +1652,7 @@ impl<'tcx, 'ctx> Analyzer<'tcx, 'ctx> { ctx, tcx, local_def_id, + analysis_key, drop_points, basic_block, body, diff --git a/src/analyze/local_def.rs b/src/analyze/local_def.rs index ccf63ae6..a4fb15b7 100644 --- a/src/analyze/local_def.rs +++ b/src/analyze/local_def.rs @@ -1100,14 +1100,14 @@ impl<'tcx, 'ctx> Analyzer<'tcx, 'ctx> { } bty.set_precondition(inv); self.ctx - .register_basic_block_ty_with_precondition(self.local_def_id, bb, bty); + .register_basic_block_ty_with_precondition(self.analysis_key(), bb, bty); } else if analyze::basic_block::needs_own_precondition(&self.body, bb) { let bty = self .type_builder .for_template(&mut self.ctx) .build_basic_block(&self.body, live_locals, ret_ty); self.ctx - .register_basic_block_ty_with_precondition(self.local_def_id, bb, bty); + .register_basic_block_ty_with_precondition(self.analysis_key(), bb, bty); } else { // The block inherits its predecessor's outgoing env state as its // precondition, materialized lazily during the predecessor's @@ -1116,7 +1116,7 @@ impl<'tcx, 'ctx> Analyzer<'tcx, 'ctx> { .type_builder .build_basic_block(&self.body, live_locals, ret_ty); self.ctx - .register_basic_block_ty_without_precondition(self.local_def_id, bb, bty); + .register_basic_block_ty_without_precondition(self.analysis_key(), bb, bty); }; } } @@ -1131,11 +1131,11 @@ impl<'tcx, 'ctx> Analyzer<'tcx, 'ctx> { } let rty = self .ctx - .basic_block_ty_with_precondition(self.local_def_id, bb) + .basic_block_ty_with_precondition(self.analysis_key(), bb) .clone(); let drop_points = self.drop_points[&bb].clone(); self.ctx - .basic_block_analyzer(self.local_def_id, bb, self.owner_fn_id) + .basic_block_analyzer(self.analysis_key(), bb) .body(self.body.clone()) .drop_points(drop_points) .run(&rty, expected_fn_ty); @@ -1242,7 +1242,7 @@ impl<'tcx, 'ctx> Analyzer<'tcx, 'ctx> { fn assert_entry(&mut self, expected: &rty::RefinedType) { let mut entry_ty = self .ctx - .basic_block_ty_with_precondition(self.local_def_id, mir::START_BLOCK) + .basic_block_ty_with_precondition(self.analysis_key(), mir::START_BLOCK) .clone(); tracing::debug!(expected = %expected.display(), entry = %entry_ty.display(), "assert_entry before"); let mut expected = expected.ty.as_function().cloned().unwrap(); @@ -1286,6 +1286,10 @@ impl<'tcx, 'ctx> Analyzer<'tcx, 'ctx> { self.local_def_id } + pub fn analysis_key(&self) -> analyze::AnalysisKey<'tcx> { + analyze::AnalysisKey::new(self.local_def_id, self.generic_args, self.owner_fn_id) + } + pub fn owner_fn_id(&mut self, owner_fn_id: DefId) -> &mut Self { tracing::debug!( "change owner_fn_id from {:?} to {:?}.", From 5b9d65e22e9337ed243fb7aee51eccc0e72ee91d Mon Sep 17 00:00:00 2001 From: coeff-aij <175928954+coeff-aij@users.noreply.github.com> Date: Wed, 9 Sep 2026 01:03:21 +0900 Subject: [PATCH 134/142] Note what ties a generic body's re-analysis to its caller `InstantiationKey` carries `caller_def_id`, so a generic body is re-analyzed once per (type arguments, calling function) rather than once per monomorphization. Record why it cannot simply be dropped -- the caller's `owner_fn_id` is what interprets a `ParamTy`'s index, so removing it would silently conflate type parameters declared in different items -- and what would have to change for this to become a monomorphization cache. Co-Authored-By: Claude Opus 5 (1M context) --- src/analyze.rs | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/src/analyze.rs b/src/analyze.rs index c8a4f761..11b76fba 100644 --- a/src/analyze.rs +++ b/src/analyze.rs @@ -188,6 +188,18 @@ struct GenericDefTy<'tcx> { rty: Option, } +// TODO: key this on the callee and its arguments alone, once analyzing a body no longer +// depends on who is calling. +// +// `caller_def_id` is here because the body of a generic def is re-analyzed under the +// caller's `owner_fn_id`, and that owner is what interprets a `ParamTy`'s index: without +// it, `TypeBuilder::param_def_id` resolves index 0 of one def and index 0 of another to +// the same declaration site. It also selects the `TypingEnv` normalization runs in and +// mints the closure pre/post forall-pred identities. So a body is analyzed once per +// (type arguments, calling function) rather than once per monomorphization, which is +// superlinear in call sites -- the shape that a small generic function called from many +// places runs into. Giving the body analysis the callee as its owner would make it +// caller-independent and let this be a monomorphization cache. #[derive(Debug, Clone, Copy, Hash, PartialEq, Eq)] struct InstantiationKey<'tcx> { generic_args: mir_ty::GenericArgsRef<'tcx>, From 8acdcdc7669e71ffc051c72251416e62e1274c5b Mon Sep 17 00:00:00 2001 From: coeff-aij <175928954+coeff-aij@users.noreply.github.com> Date: Wed, 9 Sep 2026 02:02:02 +0900 Subject: [PATCH 135/142] Pin how a generic function's contract reaches a generic caller Two shapes that no test covered, both of which a change to how a def is routed between the concrete and the generic analysis would silently alter. A generic function whose signature does not mention its type parameter is analyzed with a concrete contract, so a caller that is itself generic still learns its result. Routing such a def to the generic analysis instead would leave the contract to a re-analysis that a call at type arguments which are still type parameters never triggers, and the caller would accept anything from that call onwards. An annotated generic function contributes its contract at such a call site, where an inferred one does not. The existing fn_poly_annot tests all call from `main` at concrete arguments, which is the case that does not distinguish the two. Co-Authored-By: Claude Opus 5 (1M context) --- tests/ui/fail/fn_poly_annot_generic_call.rs | 19 +++++++++++++++++++ .../fail/fn_poly_body_param_generic_call.rs | 17 +++++++++++++++++ tests/ui/pass/fn_poly_annot_generic_call.rs | 19 +++++++++++++++++++ .../pass/fn_poly_body_param_generic_call.rs | 17 +++++++++++++++++ 4 files changed, 72 insertions(+) create mode 100644 tests/ui/fail/fn_poly_annot_generic_call.rs create mode 100644 tests/ui/fail/fn_poly_body_param_generic_call.rs create mode 100644 tests/ui/pass/fn_poly_annot_generic_call.rs create mode 100644 tests/ui/pass/fn_poly_body_param_generic_call.rs diff --git a/tests/ui/fail/fn_poly_annot_generic_call.rs b/tests/ui/fail/fn_poly_annot_generic_call.rs new file mode 100644 index 00000000..1e1179ce --- /dev/null +++ b/tests/ui/fail/fn_poly_annot_generic_call.rs @@ -0,0 +1,19 @@ +//@error-in-other-file: Unsat +//@compile-flags: -C debug-assertions=off +//@rustc-env: THRUST_SOLVER=tests/thrust-pcsat-wrapper COAR_IMAGE=coar:latest + +// An annotated generic function contributes its contract at a call site whose type +// arguments are still type parameters, where an inferred contract would not. + +#[thrust_macros::requires(true)] +#[thrust_macros::ensures(result == 5)] +fn five(_t: T) -> i64 { + 5 +} + +#[thrust::callable] +fn check(t: T) { + assert!(five(t) == 6); +} + +fn main() {} diff --git a/tests/ui/fail/fn_poly_body_param_generic_call.rs b/tests/ui/fail/fn_poly_body_param_generic_call.rs new file mode 100644 index 00000000..ebf8c05c --- /dev/null +++ b/tests/ui/fail/fn_poly_body_param_generic_call.rs @@ -0,0 +1,17 @@ +//@error-in-other-file: Unsat +//@compile-flags: -C debug-assertions=off +//@rustc-env: THRUST_SOLVER=tests/thrust-pcsat-wrapper COAR_IMAGE=coar:latest + +// A generic function whose signature does not mention its type parameter keeps a +// concrete contract, so a caller that is itself generic still learns its result. + +fn five() -> i64 { + 5 +} + +#[thrust::callable] +fn check() { + assert!(five::() == 6); +} + +fn main() {} diff --git a/tests/ui/pass/fn_poly_annot_generic_call.rs b/tests/ui/pass/fn_poly_annot_generic_call.rs new file mode 100644 index 00000000..c6424bb8 --- /dev/null +++ b/tests/ui/pass/fn_poly_annot_generic_call.rs @@ -0,0 +1,19 @@ +//@check-pass +//@compile-flags: -C debug-assertions=off +//@rustc-env: THRUST_SOLVER=tests/thrust-pcsat-wrapper COAR_IMAGE=coar:latest + +// An annotated generic function contributes its contract at a call site whose type +// arguments are still type parameters, where an inferred contract would not. + +#[thrust_macros::requires(true)] +#[thrust_macros::ensures(result == 5)] +fn five(_t: T) -> i64 { + 5 +} + +#[thrust::callable] +fn check(t: T) { + assert!(five(t) == 5); +} + +fn main() {} diff --git a/tests/ui/pass/fn_poly_body_param_generic_call.rs b/tests/ui/pass/fn_poly_body_param_generic_call.rs new file mode 100644 index 00000000..2393edbd --- /dev/null +++ b/tests/ui/pass/fn_poly_body_param_generic_call.rs @@ -0,0 +1,17 @@ +//@check-pass +//@compile-flags: -C debug-assertions=off +//@rustc-env: THRUST_SOLVER=tests/thrust-pcsat-wrapper COAR_IMAGE=coar:latest + +// A generic function whose signature does not mention its type parameter keeps a +// concrete contract, so a caller that is itself generic still learns its result. + +fn five() -> i64 { + 5 +} + +#[thrust::callable] +fn check() { + assert!(five::() == 5); +} + +fn main() {} From 9301494c0d0030fad995ad12e144b6a378566001 Mon Sep 17 00:00:00 2001 From: coeff-aij <175928954+coeff-aij@users.noreply.github.com> Date: Wed, 9 Sep 2026 15:54:35 +0900 Subject: [PATCH 136/142] Declare the forall default an empty array references `collect_forall_defaults` decided which `default_` constants to declare by scanning the stored clause AST for `Term::ForallDefault`, and treated `Term::ArrayEmpty` as a leaf. But the SMT-LIB2 writer synthesises `Term::default_for(elem)` for an empty array at print time, so an array over an abstract element sort emitted `default_a0` with no declaration and CoAR rejected the file with `default_a0 is not bound`. Ask `default_for` itself which defaults the writer will reference, so the two cannot drift. Co-Authored-By: Claude Opus 5 --- src/chc.rs | 26 +++++++++++++++++++++++++- 1 file changed, 25 insertions(+), 1 deletion(-) diff --git a/src/chc.rs b/src/chc.rs index 8f517e41..7cee9b80 100644 --- a/src/chc.rs +++ b/src/chc.rs @@ -2471,6 +2471,10 @@ fn collect_forall_defaults(term: &Term, used: &mut HashSet collect_forall_defaults(t, used), + // An empty array carries no default of its own in the AST: the SMT-LIB2 + // writer synthesises `default_for(elem)` for it at print time, so ask the + // same function which defaults that will reference. + Term::ArrayEmpty(_, elem) => collect_forall_defaults(&Term::default_for(elem), used), Term::DatatypeCtor(_, _, args) => { for t in args { collect_forall_defaults(t, used); @@ -2482,7 +2486,6 @@ fn collect_forall_defaults(term: &Term, used: &mut HashSet {} } } @@ -2512,6 +2515,27 @@ mod tests { assert_eq!(smt.matches("default_a0").count(), 2); } + #[test] + fn declares_forall_default_reached_only_through_an_empty_array() { + let mut system = System::default(); + let idx = system.new_forall_sort(DebugInfo::default()); + let seq_sort = Sort::array(Sort::int(), Sort::forall(idx)); + let empty = Term::default_for(&seq_sort); + let body = Atom::new( + Pred::Known(KnownPred::EQUAL), + vec![empty, Term::var(0usize.into())], + ); + system.push_clause(Clause { + vars: [seq_sort].into_iter().collect(), + head: Atom::new(Pred::UserDefined(UserDefinedPred::new("p".into())), vec![]), + body: body.into(), + debug_info: DebugInfo::default(), + }); + + let smt = system.smtlib2().to_string(); + assert_eq!(smt.matches("(declare-const default_a0 a0)").count(), 1); + } + #[test] fn does_not_declare_default_for_unused_forall_sort() { let mut system = System::default(); From c0cfee579219680a5a8d1a0815e652272a198502 Mon Sep 17 00:00:00 2001 From: coeff-aij <175928954+coeff-aij@users.noreply.github.com> Date: Wed, 9 Sep 2026 15:58:06 +0900 Subject: [PATCH 137/142] Keep a `Self::Assoc` projection resolvable when lifted out of a trait impl MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Lifting a formula out of an impl method replaced the type `Self` with the impl's self type, turning the projection `Self::Item` into `>::Item`. `Self` in a trait impl says which trait to look in; a bare ADT does not, so rustc rejected the lifted `#[thrust::formula_fn]` with `E0223 ambiguous associated type` — and the type annotations of nearby ghost terms failed to infer as a consequence. Carry the implemented trait along and emit ` as Iterator>::Item`. The trait-method branch is unaffected: it substitutes a type parameter, whose bounds resolve the projection on their own. Co-Authored-By: Claude Opus 5 --- tests/ui/fail/traits/ghost_in_generic_impl.rs | 51 +++++++++++++++++++ tests/ui/pass/traits/ghost_in_generic_impl.rs | 51 +++++++++++++++++++ thrust-macros/src/formula_fn_lifting.rs | 21 ++++++-- 3 files changed, 120 insertions(+), 3 deletions(-) create mode 100644 tests/ui/fail/traits/ghost_in_generic_impl.rs create mode 100644 tests/ui/pass/traits/ghost_in_generic_impl.rs diff --git a/tests/ui/fail/traits/ghost_in_generic_impl.rs b/tests/ui/fail/traits/ghost_in_generic_impl.rs new file mode 100644 index 00000000..8de35bd1 --- /dev/null +++ b/tests/ui/fail/traits/ghost_in_generic_impl.rs @@ -0,0 +1,51 @@ +//@error-in-other-file: Unsat +//@compile-flags: -C debug-assertions=off -A unused-variables +//@rustc-env: THRUST_SOLVER=tests/thrust-pcsat-wrapper COAR_IMAGE=coar:latest + +use thrust_models::model::Int; +use thrust_models::{Ghost, Model}; + +#[thrust_macros::context] +trait A { + type Item; + + #[thrust_macros::requires(true)] + #[thrust_macros::ensures(true)] + fn f(&mut self); +} + +#[thrust_macros::requires(g == 1)] +fn expect_one(g: Ghost) { + let _ = g; +} + +struct W { + inner: I, +} + +impl Model for W { + type Ty = W; +} + +// The trait declares an associated type, so the formula lifted out of `ghost!` +// carries a `Self::Item: Model` bound. `Self` reaches the lifted function as the +// impl's self type, where the projection needs the trait to stay unambiguous. +#[thrust_macros::context] +impl A for W +where + I: A + Model, + ::Item: Model, + <::Item as Model>::Ty: PartialEq, + ::Ty: PartialEq, +{ + type Item = I::Item; + + fn f(&mut self) { + let one: i64 = 2; + let g = thrust_macros::ghost!(|one: i64| -> Int { one }); + expect_one(g); + self.inner.f(); + } +} + +fn main() {} diff --git a/tests/ui/pass/traits/ghost_in_generic_impl.rs b/tests/ui/pass/traits/ghost_in_generic_impl.rs new file mode 100644 index 00000000..4d0422b2 --- /dev/null +++ b/tests/ui/pass/traits/ghost_in_generic_impl.rs @@ -0,0 +1,51 @@ +//@check-pass +//@compile-flags: -C debug-assertions=off -A unused-variables +//@rustc-env: THRUST_SOLVER=tests/thrust-pcsat-wrapper COAR_IMAGE=coar:latest + +use thrust_models::model::Int; +use thrust_models::{Ghost, Model}; + +#[thrust_macros::context] +trait A { + type Item; + + #[thrust_macros::requires(true)] + #[thrust_macros::ensures(true)] + fn f(&mut self); +} + +#[thrust_macros::requires(g == 1)] +fn expect_one(g: Ghost) { + let _ = g; +} + +struct W { + inner: I, +} + +impl Model for W { + type Ty = W; +} + +// The trait declares an associated type, so the formula lifted out of `ghost!` +// carries a `Self::Item: Model` bound. `Self` reaches the lifted function as the +// impl's self type, where the projection needs the trait to stay unambiguous. +#[thrust_macros::context] +impl A for W +where + I: A + Model, + ::Item: Model, + <::Item as Model>::Ty: PartialEq, + ::Ty: PartialEq, +{ + type Item = I::Item; + + fn f(&mut self) { + let one: i64 = 1; + let g = thrust_macros::ghost!(|one: i64| -> Int { one }); + expect_one(g); + self.inner.f(); + } +} + +fn main() {} diff --git a/thrust-macros/src/formula_fn_lifting.rs b/thrust-macros/src/formula_fn_lifting.rs index 12b16d43..3fb5de07 100644 --- a/thrust-macros/src/formula_fn_lifting.rs +++ b/thrust-macros/src/formula_fn_lifting.rs @@ -155,11 +155,17 @@ pub fn lift( match outer { FnOuterItem::ItemImpl(item_impl) => { - // `Self` in an impl method context: rewrite it to the concrete self type everywhere - // TODO: Support generic/trait impl + // `Self` in an impl method context: rewrite it to the concrete self type + // everywhere. In a trait impl the projection `Self::Item` names the + // implemented trait's associated item, so carry that trait along: without it + // the substituted `>::Item` no longer says which trait to look in. let self_ty = &item_impl.self_ty; let mut rewriter = SelfTypeRewriter { to: *self_ty.clone(), + trait_: item_impl + .trait_ + .as_ref() + .map(|(trait_path, _)| trait_path.clone()), }; for param in &mut params { rewriter.visit_fn_arg_mut(param); @@ -179,6 +185,9 @@ pub fn lift( let mut rewriter = SelfTypeRewriter { to: syn::parse_quote!(#synth), + // `#synth` is a type parameter bound by the trait below, so + // `<#synth>::Item` resolves through that bound on its own. + trait_: None, }; for param in &mut params { rewriter.visit_fn_arg_mut(param); @@ -299,6 +308,9 @@ impl VisitMut for SelfValueRewriter { struct SelfTypeRewriter { to: syn::Type, + /// The trait an enclosing `impl` implements, used to qualify a `Self::Assoc` + /// projection as `<#to as #trait_>::Assoc`. + trait_: Option, } impl VisitMut for SelfTypeRewriter { @@ -325,7 +337,10 @@ impl VisitMut for SelfTypeRewriter { *ty = self.to.clone(); } else { let to = &self.to; - *ty = syn::parse_quote!(<#to>::#tail) + *ty = match &self.trait_ { + Some(trait_) => syn::parse_quote!(<#to as #trait_>::#tail), + None => syn::parse_quote!(<#to>::#tail), + } }; } From 79c531583563d6e9aa818bbf4cb47920ff9c9234 Mon Sep 17 00:00:00 2001 From: coeff-aij <175928954+coeff-aij@users.noreply.github.com> Date: Wed, 9 Sep 2026 16:08:29 +0900 Subject: [PATCH 138/142] Resolve an impl's closure type parameter at the caller's arguments Registering the contract of an `Fn`-bounded type parameter read the parameter's index in the generics of the function being analysed. A bound declared on an impl block was taken straight from `predicates_of(impl)`, which no instantiation has touched, so an external call into that impl looked the impl's own parameter up in the caller's table and hit `unknown type param idx`. Resolve such a parameter through the arguments the analysis runs with before using it, for both the sort it builds and the key it registers under. A parameter that resolves to another generic caller's parameter is now recorded against that one; a parameter that resolves to a concrete callable needs no parameter-keyed contract at all, since the callable carries its own. `build_closure_type_for_param` tried to do this by instantiating the `ParamTy` itself, which is an identity by construction -- instantiating a `ParamTy` can only yield a `ParamTy`, never the concrete argument. Bind the type instead. Co-Authored-By: Claude Opus 5 --- src/analyze/local_def.rs | 16 ++++++--- src/refine/template.rs | 34 ++++++++++++++++--- tests/ui/fail/closure_field_impl_call.rs | 29 ++++++++++++++++ .../fail/closure_field_impl_call_generic.rs | 34 +++++++++++++++++++ tests/ui/pass/closure_field_impl_call.rs | 29 ++++++++++++++++ .../pass/closure_field_impl_call_generic.rs | 34 +++++++++++++++++++ 6 files changed, 167 insertions(+), 9 deletions(-) create mode 100644 tests/ui/fail/closure_field_impl_call.rs create mode 100644 tests/ui/fail/closure_field_impl_call_generic.rs create mode 100644 tests/ui/pass/closure_field_impl_call.rs create mode 100644 tests/ui/pass/closure_field_impl_call_generic.rs diff --git a/src/analyze/local_def.rs b/src/analyze/local_def.rs index a4fb15b7..a6093f01 100644 --- a/src/analyze/local_def.rs +++ b/src/analyze/local_def.rs @@ -326,6 +326,9 @@ impl<'tcx, 'ctx> Analyzer<'tcx, 'ctx> { } _ => None, }; + // No resolution here: `sig` comes from the body, which `Self::generic_args` + // has already instantiated, so `param_ty` is a parameter of the caller + // already -- applying the arguments a second time would be out of range. if let Some(param_ty) = param_ty { if let Some(fun_ty) = self.type_builder.build_closure_type_for_param( param_ty, @@ -391,15 +394,20 @@ impl<'tcx, 'ctx> Analyzer<'tcx, 'ctx> { let mir_ty::TyKind::Param(p) = trait_ref.self_ty().kind() else { continue; }; + // `p` is declared by the impl block. Only the parameter this analysis's + // arguments map it to lives in the index space `param_local_idx` reads. + let Some(p) = self.type_builder.resolve_param_ty(*p, self.generic_args) else { + continue; + }; if let Some(fun_ty) = self.type_builder.build_closure_type_for_param( - *p, + p, impl_local_def_id, - self.tcx.mk_args(&[]), + self.generic_args, ) { self.type_builder.register_closure_type_param( analyze::TypeParam::GenericType { - param_def_id: self.type_builder.param_def_id(p), - local_idx: self.type_builder.param_local_idx(p), + param_def_id: self.type_builder.param_def_id(&p), + local_idx: self.type_builder.param_local_idx(&p), }, fun_ty, ); diff --git a/src/refine/template.rs b/src/refine/template.rs index 22f6c86e..c0dfca0a 100644 --- a/src/refine/template.rs +++ b/src/refine/template.rs @@ -596,17 +596,41 @@ impl<'tcx> TypeBuilder<'tcx> { /// Returns `None` if `param_ty` has no `Fn` / `FnMut` / `FnOnce` trait bound. /// As a side effect, the closure pre/post forall predicates are registered /// with the [`chc::System`]. + /// Resolves `param_ty`, declared by some other item, at the generic arguments + /// this analysis runs with. + /// + /// Returns the type parameter those arguments map it to -- which is the one the + /// current [`Self::param_local_idx`] can read -- or `None` when it maps to a + /// concrete type, which carries its own contract and needs no parameter-keyed one. + pub fn resolve_param_ty( + &self, + param_ty: mir_ty::ParamTy, + generic_args: mir_ty::GenericArgsRef<'tcx>, + ) -> Option { + if generic_args.is_empty() { + return Some(param_ty); + } + // Bind the *type*, not the `ParamTy`: instantiating a `ParamTy` can only ever + // return a `ParamTy`, so it cannot substitute a concrete argument. + let ty = + mir_ty::EarlyBinder::bind(param_ty.to_ty(self.tcx)).instantiate(self.tcx, generic_args); + match ty.kind() { + mir_ty::TyKind::Param(p) => Some(*p), + _ => None, + } + } + + /// Builds the contract of a closure-typed parameter from its `Fn` bound. + /// + /// `param_ty` must already be resolved for the current analysis + /// ([`Self::resolve_param_ty`]); `generic_args` instantiates `local_def_id`'s + /// predicates so the bound is found at those same arguments. pub fn build_closure_type_for_param( &self, param_ty: mir_ty::ParamTy, local_def_id: rustc_hir::def_id::LocalDefId, generic_args: mir_ty::GenericArgsRef<'tcx>, ) -> Option { - let param_ty = if !generic_args.is_empty() { - mir_ty::EarlyBinder::bind(param_ty).instantiate(self.tcx, generic_args) - } else { - param_ty - }; // `predicates_of(..).predicates` holds only the predicates written on the // function itself; a bound such as `F: FnMut(..)` on the enclosing impl or // trait lives in the parent's predicates. `instantiate` and diff --git a/tests/ui/fail/closure_field_impl_call.rs b/tests/ui/fail/closure_field_impl_call.rs new file mode 100644 index 00000000..512ff4de --- /dev/null +++ b/tests/ui/fail/closure_field_impl_call.rs @@ -0,0 +1,29 @@ +//@error-in-other-file: Unsat +//@compile-flags: -C debug-assertions=off -A unused-variables +//@rustc-env: THRUST_SOLVER=tests/thrust-pcsat-wrapper COAR_IMAGE=coar:latest + +struct Holder { + func: F, +} + +impl thrust_models::Model for Holder { + type Ty = Holder>; +} + +// The `Fn` bound sits on the impl block rather than on the method, so a call +// from outside has to resolve `F` at the arguments the caller supplies. The body +// deliberately leaves the closure alone: calling it would drag in its own +// precondition, which is a separate question. +#[thrust_macros::context] +impl i64> Holder { + #[thrust_macros::requires(x > 0)] + #[thrust_macros::ensures(result > 0)] + fn twice(&self, x: i64) -> i64 { + x + x + } +} + +fn main() { + let h = Holder { func: |y: i64| y + 1 }; + let _ = h.twice(0); +} diff --git a/tests/ui/fail/closure_field_impl_call_generic.rs b/tests/ui/fail/closure_field_impl_call_generic.rs new file mode 100644 index 00000000..3bcd11d8 --- /dev/null +++ b/tests/ui/fail/closure_field_impl_call_generic.rs @@ -0,0 +1,34 @@ +//@error-in-other-file: Unsat +//@compile-flags: -C debug-assertions=off -A unused-variables +//@rustc-env: THRUST_SOLVER=tests/thrust-pcsat-wrapper COAR_IMAGE=coar:latest + +struct Holder { + func: F, +} + +impl thrust_models::Model for Holder { + type Ty = Holder>; +} + +#[thrust_macros::context] +impl i64> Holder { + #[thrust_macros::requires(x > 0)] + #[thrust_macros::ensures(result > 0)] + fn twice(&self, x: i64) -> i64 { + x + x + } +} + +// Here the impl's `F` is filled by another generic function's parameter instead +// of a concrete closure, so it stays a type parameter -- of `outer`, not of the +// impl that declared it. +#[thrust_macros::requires(x > 0)] +#[thrust_macros::ensures(result > 0)] +fn outer i64>(g: G, x: i64) -> i64 { + let h = Holder { func: g }; + h.twice(x) +} + +fn main() { + let _ = outer(|y: i64| y + 1, 0); +} diff --git a/tests/ui/pass/closure_field_impl_call.rs b/tests/ui/pass/closure_field_impl_call.rs new file mode 100644 index 00000000..7bb7a37d --- /dev/null +++ b/tests/ui/pass/closure_field_impl_call.rs @@ -0,0 +1,29 @@ +//@check-pass +//@compile-flags: -C debug-assertions=off -A unused-variables +//@rustc-env: THRUST_SOLVER=tests/thrust-pcsat-wrapper COAR_IMAGE=coar:latest + +struct Holder { + func: F, +} + +impl thrust_models::Model for Holder { + type Ty = Holder>; +} + +// The `Fn` bound sits on the impl block rather than on the method, so a call +// from outside has to resolve `F` at the arguments the caller supplies. The body +// deliberately leaves the closure alone: calling it would drag in its own +// precondition, which is a separate question. +#[thrust_macros::context] +impl i64> Holder { + #[thrust_macros::requires(x > 0)] + #[thrust_macros::ensures(result > 0)] + fn twice(&self, x: i64) -> i64 { + x + x + } +} + +fn main() { + let h = Holder { func: |y: i64| y + 1 }; + let _ = h.twice(1); +} diff --git a/tests/ui/pass/closure_field_impl_call_generic.rs b/tests/ui/pass/closure_field_impl_call_generic.rs new file mode 100644 index 00000000..a4b4a122 --- /dev/null +++ b/tests/ui/pass/closure_field_impl_call_generic.rs @@ -0,0 +1,34 @@ +//@check-pass +//@compile-flags: -C debug-assertions=off -A unused-variables +//@rustc-env: THRUST_SOLVER=tests/thrust-pcsat-wrapper COAR_IMAGE=coar:latest + +struct Holder { + func: F, +} + +impl thrust_models::Model for Holder { + type Ty = Holder>; +} + +#[thrust_macros::context] +impl i64> Holder { + #[thrust_macros::requires(x > 0)] + #[thrust_macros::ensures(result > 0)] + fn twice(&self, x: i64) -> i64 { + x + x + } +} + +// Here the impl's `F` is filled by another generic function's parameter instead +// of a concrete closure, so it stays a type parameter -- of `outer`, not of the +// impl that declared it. +#[thrust_macros::requires(x > 0)] +#[thrust_macros::ensures(result > 0)] +fn outer i64>(g: G, x: i64) -> i64 { + let h = Holder { func: g }; + h.twice(x) +} + +fn main() { + let _ = outer(|y: i64| y + 1, 1); +} From 09d408ebeb5d68814bef5550644db28852ee6cb6 Mon Sep 17 00:00:00 2001 From: coeff-aij <175928954+coeff-aij@users.noreply.github.com> Date: Wed, 9 Sep 2026 19:52:53 +0900 Subject: [PATCH 139/142] Let an `FnMut` precondition name the closure's current upvars An `FnMut` closure receives its upvars behind a `&mut`, and its precondition took that whole `Mut` pair. The pair's prophecy is still unconstrained where the precondition has to be discharged -- the borrow resolves it only after the call -- so `pre!(f(..))`, which names the upvars as they are, could never reach the obligation the call raises, and every `FnMut` precondition needed a `forall` over the prophecy before it said anything at all. Take the current value of the upvars in the precondition instead. A precondition is a property of the state the call starts from, and the two states stay related by the postcondition, which is unchanged. `closure_trait_call` now reports the `Fn` trait it resolved, so the projection keys off that rather than off the shape of the receiver type. Specs that spelled the receiver out as `Mut::new(f, g)` still mean what they meant, since the projection drops `g`. The two-call tests gain the precondition they were missing: their upvars have to satisfy it in every state the calls start from, which needs one binder now instead of two. Their `fail` twins keep their old specification -- with the quantified precondition the solver does not answer the negative direction within 180s. Co-Authored-By: Claude Opus 5 --- src/chc.rs | 2 +- src/refine/template.rs | 66 +++++++++++++++---- tests/ui/fail/closure_mut_capture_pre_post.rs | 4 +- tests/ui/fail/traits/field_closure_call.rs | 24 +++++++ tests/ui/pass/closure_mut_capture_pre_post.rs | 9 ++- tests/ui/pass/closure_receiver_mut_model.rs | 3 +- .../pass/closure_receiver_mut_model_byval.rs | 5 +- tests/ui/pass/traits/field_closure_call.rs | 8 +-- 8 files changed, 97 insertions(+), 24 deletions(-) create mode 100644 tests/ui/fail/traits/field_closure_call.rs diff --git a/src/chc.rs b/src/chc.rs index 7cee9b80..0ad8b2de 100644 --- a/src/chc.rs +++ b/src/chc.rs @@ -265,7 +265,7 @@ impl Sort { } } - fn deref(self) -> Self { + pub fn deref(self) -> Self { match self { Sort::Box(s) => *s, Sort::Mut(s) => *s, diff --git a/src/refine/template.rs b/src/refine/template.rs index c0dfca0a..211fe11b 100644 --- a/src/refine/template.rs +++ b/src/refine/template.rs @@ -85,6 +85,37 @@ pub struct TypeBuilder<'tcx> { system: Rc>, } +// TODO: Fold `TypeBuilder::closure_trait_ret` into `closure_trait_call` so that one function +// resolves a closure-typed parameter's whole call signature instead of two halves reporting the +// parameters and the return type separately. +/// What a `Fn`/`FnMut`/`FnOnce` bound says about calling a closure-typed parameter: the parameters +/// of the call, and which of the three traits the bound is. The return type comes separately, from +/// [`TypeBuilder::closure_trait_ret`]. +struct ClosureTraitCall { + params: IndexVec>, + kind: mir_ty::ClosureKind, +} + +/// The sort a closure precondition takes its upvars in. +/// +/// An `FnMut` closure receives its upvars behind a `&mut`, whose prophecy is still unconstrained +/// where the precondition has to be discharged; the precondition therefore names their current +/// value rather than the `Mut` pair. `Fn` and `FnOnce` carry no prophecy to drop. +fn pre_upvars_sort(upvars: chc::Sort, closure_kind: mir_ty::ClosureKind) -> chc::Sort { + match closure_kind { + mir_ty::ClosureKind::FnMut => upvars.deref(), + mir_ty::ClosureKind::Fn | mir_ty::ClosureKind::FnOnce => upvars, + } +} + +/// The upvars term matching [`pre_upvars_sort`]. +fn pre_upvars_term(upvars: chc::Term, closure_kind: mir_ty::ClosureKind) -> chc::Term { + match closure_kind { + mir_ty::ClosureKind::FnMut => upvars.mut_current(), + mir_ty::ClosureKind::Fn | mir_ty::ClosureKind::FnOnce => upvars, + } +} + impl<'tcx> TypeBuilder<'tcx> { pub fn new( tcx: mir_ty::TyCtxt<'tcx>, @@ -519,7 +550,7 @@ impl<'tcx> TypeBuilder<'tcx> { } } - /// Extracts the parameter list for a `Fn` / `FnMut` / `FnOnce` trait predicate + /// Extracts the [`ClosureTraitCall`] of a `Fn` / `FnMut` / `FnOnce` trait predicate /// whose `Self` type matches `param_ty`. Returns `None` otherwise. /// /// The first parameter is the closure value (wrapped in a `&` / `&mut` pointer @@ -527,11 +558,11 @@ impl<'tcx> TypeBuilder<'tcx> { /// as a single tuple matching the call-site shape produced by /// `>::call(...)`. #[tracing::instrument(skip(self))] - fn closure_trait_args( + fn closure_trait_call( &self, param_ty: mir_ty::ParamTy, pred: mir_ty::TraitPredicate<'tcx>, - ) -> Option>> { + ) -> Option { let trait_ref = pred.trait_ref; if trait_ref.self_ty() != param_ty.to_ty(self.tcx) { return None; @@ -560,7 +591,10 @@ impl<'tcx> TypeBuilder<'tcx> { .collect(); tracing::debug!("found the signature for closure trait: {params:#?}"); - Some(params) + Some(ClosureTraitCall { + params, + kind: closure_kind, + }) } /// Extracts the return type refinement for `::Output` projection @@ -643,8 +677,11 @@ impl<'tcx> TypeBuilder<'tcx> { }; let mut predicates = predicates.predicates.into_iter(); - let mut params = predicates.clone().find_map(|clause| { - self.closure_trait_args(param_ty, clause.as_trait_clause()?.skip_binder()) + let ClosureTraitCall { + mut params, + kind: closure_kind, + } = predicates.clone().find_map(|clause| { + self.closure_trait_call(param_ty, clause.as_trait_clause()?.skip_binder()) })?; let mut ret = predicates.find_map(|clause| { self.closure_trait_ret(param_ty, clause.as_projection_clause()?.skip_binder()) @@ -663,11 +700,14 @@ impl<'tcx> TypeBuilder<'tcx> { let mut params_sort: Vec = params.iter().map(|rty| rty.ty.to_sort()).collect(); let ret_sort = ret.ty.to_sort(); + let mut pre_params_sort = params_sort.clone(); + pre_params_sort[0] = pre_upvars_sort(pre_params_sort[0].clone(), closure_kind); + let pre_pred = refine::closure_pre_forall_pred( self.tcx, self.owner_fn_id, type_params.clone(), - params_sort.clone(), + pre_params_sort, ); self.system .borrow_mut() @@ -684,12 +724,14 @@ impl<'tcx> TypeBuilder<'tcx> { .last() .expect("Closure should have at least one argument.") .extend_refinement({ + // This refinement rides on the closure's last parameter, so `value` names that + // parameter here and `free(idx)` the earlier ones: a closure taking no logical + // argument has its upvars in the `value` slot. let (args_front, _args_last) = args.split_at(args.len() - 1); - chc::Atom::new( - pre_pred.into(), - [args_front, std::slice::from_ref(&value.clone())].concat(), - ) - .into() + let mut pre_args: Vec<_> = + [args_front, std::slice::from_ref(&value.clone())].concat(); + pre_args[0] = pre_upvars_term(pre_args[0].clone(), closure_kind); + chc::Atom::new(pre_pred.into(), pre_args).into() }); ret.extend_refinement( diff --git a/tests/ui/fail/closure_mut_capture_pre_post.rs b/tests/ui/fail/closure_mut_capture_pre_post.rs index 83fcbbe9..8570ad6f 100644 --- a/tests/ui/fail/closure_mut_capture_pre_post.rs +++ b/tests/ui/fail/closure_mut_capture_pre_post.rs @@ -1,8 +1,10 @@ //@error-in-other-file: Unsat //@compile-flags: -C debug-assertions=off //@rustc-env: THRUST_SOLVER=tests/thrust-pcsat-wrapper COAR_IMAGE=coar:latest +use thrust_models::{exists, model::Mut}; + #[thrust_macros::requires(thrust_macros::pre!(f()))] -#[thrust_macros::ensures(thrust_macros::post!(f(), result))] +#[thrust_macros::ensures(exists(|g| thrust_macros::post!(Mut::new(f, g)(), result)))] fn call i64>(mut f: F) -> i64 { f() } diff --git a/tests/ui/fail/traits/field_closure_call.rs b/tests/ui/fail/traits/field_closure_call.rs new file mode 100644 index 00000000..539c16c2 --- /dev/null +++ b/tests/ui/fail/traits/field_closure_call.rs @@ -0,0 +1,24 @@ +//@error-in-other-file: Unsat +//@compile-flags: -Aunused_parens -C debug-assertions=off +//@rustc-env: THRUST_SOLVER=tests/thrust-pcsat-wrapper THRUST_SOLVER_TIMEOUT_SECS=60 COAR_IMAGE=coar:latest + +use thrust_models::model::Mut; + +struct S { + func: F, +} + +impl thrust_models::Model for S { + type Ty = S>; +} + +#[thrust_macros::context] +impl i64> S { + #[thrust_macros::requires(thrust_macros::pre!(((*self).func)(v)))] + #[thrust_macros::ensures(thrust_macros::post!(Mut::new((*self).func, (!self).func)(v), result))] + fn call(&mut self, v: i64) -> i64 { + (self.func)(v) + 1 + } +} + +fn main() {} diff --git a/tests/ui/pass/closure_mut_capture_pre_post.rs b/tests/ui/pass/closure_mut_capture_pre_post.rs index 23e8e53f..fc6282d4 100644 --- a/tests/ui/pass/closure_mut_capture_pre_post.rs +++ b/tests/ui/pass/closure_mut_capture_pre_post.rs @@ -1,10 +1,13 @@ //@check-pass //@compile-flags: -C debug-assertions=off //@rustc-env: THRUST_SOLVER=tests/thrust-pcsat-wrapper COAR_IMAGE=coar:latest -// A closure that mutates a capture receives its upvars behind a `Mut`, while the -// higher-order function names the closure by value in `pre!`/`post!`. +use thrust_models::{exists, model::Mut}; + +// A closure that mutates a capture receives its upvars behind a `Mut`. The precondition names +// their current value, so `pre!` takes the closure by value; the postcondition still relates the +// two states, and a by-value receiver cannot name the final one, so it is bound existentially. #[thrust_macros::requires(thrust_macros::pre!(f()))] -#[thrust_macros::ensures(thrust_macros::post!(f(), result))] +#[thrust_macros::ensures(exists(|g| thrust_macros::post!(Mut::new(f, g)(), result)))] fn call i64>(mut f: F) -> i64 { f() } diff --git a/tests/ui/pass/closure_receiver_mut_model.rs b/tests/ui/pass/closure_receiver_mut_model.rs index 4fb5b0df..fcef6653 100644 --- a/tests/ui/pass/closure_receiver_mut_model.rs +++ b/tests/ui/pass/closure_receiver_mut_model.rs @@ -2,8 +2,9 @@ //@compile-flags: -C debug-assertions=off //@rustc-env: THRUST_SOLVER=tests/thrust-pcsat-wrapper COAR_IMAGE=coar:latest -use thrust_models::{exists, model::{Mut, Int}}; +use thrust_models::{exists, forall, model::{Closure, Int, Mut}}; +#[thrust_macros::requires(forall(|c: Closure| thrust_macros::pre!(c())))] #[thrust_macros::ensures(exists(|g, i: Int| thrust_macros::post!(Mut::new(*f, g)(), i) && thrust_macros::post!(Mut::new(g, !f)(), result) diff --git a/tests/ui/pass/closure_receiver_mut_model_byval.rs b/tests/ui/pass/closure_receiver_mut_model_byval.rs index 23c8b243..6515710e 100644 --- a/tests/ui/pass/closure_receiver_mut_model_byval.rs +++ b/tests/ui/pass/closure_receiver_mut_model_byval.rs @@ -3,13 +3,14 @@ //@rustc-env: THRUST_SOLVER=tests/thrust-pcsat-wrapper COAR_IMAGE=coar:latest use thrust_models::{ - exists, - model::{Int, Mut}, + exists, forall, + model::{Closure, Int, Mut}, }; // Naming the closure by value leaves its upvars as the call found them, which cannot carry // the upvars from one call to the next. `Mut::new` builds the receiver instead, naming the // upvars between the two calls. +#[thrust_macros::requires(forall(|c: Closure| thrust_macros::pre!(c())))] #[thrust_macros::ensures(exists(|g, h, i: Int| thrust_macros::post!(Mut::new(f, g)(), i) && thrust_macros::post!(Mut::new(g, h)(), result) diff --git a/tests/ui/pass/traits/field_closure_call.rs b/tests/ui/pass/traits/field_closure_call.rs index a92e2df6..1b31f3eb 100644 --- a/tests/ui/pass/traits/field_closure_call.rs +++ b/tests/ui/pass/traits/field_closure_call.rs @@ -1,8 +1,8 @@ -// FIXME: Unsat; an FnMut `pre!` on this branch has to hold for a fresh prophecy of the closure state. -//@compile-flags: -C debug-assertions=off +//@check-pass +//@compile-flags: -Aunused_parens -C debug-assertions=off //@rustc-env: THRUST_SOLVER=tests/thrust-pcsat-wrapper THRUST_SOLVER_TIMEOUT_SECS=60 COAR_IMAGE=coar:latest -use thrust_models::{exists, model::Mut}; +use thrust_models::model::Mut; struct S { func: F, @@ -14,7 +14,7 @@ impl thrust_models::Model for S { #[thrust_macros::context] impl i64> S { - #[thrust_macros::requires(exists(|g| thrust_macros::pre!(Mut::new((*self).func, g)(v))))] + #[thrust_macros::requires(thrust_macros::pre!(((*self).func)(v)))] #[thrust_macros::ensures(thrust_macros::post!(Mut::new((*self).func, (!self).func)(v), result))] fn call(&mut self, v: i64) -> i64 { (self.func)(v) From 1ae772552aed0fe16b0d8d9abfccb83e96b2a3b7 Mon Sep 17 00:00:00 2001 From: coeff-aij <175928954+coeff-aij@users.noreply.github.com> Date: Wed, 9 Sep 2026 20:43:38 +0900 Subject: [PATCH 140/142] Adopt the ghost-history test pairs from iterator-adapters Twelve pass/fail pairs whose specifications verify in both directions on this branch: a ghost `Seq` grown by a loop (`ghost_seq_loop`, and its `forall` variant), the same history carried through a trait loop (`traits/ghost_produced`, `traits/ghost_step_chain`, `traits/ghost_count`), fold specified with an `Fn` and that history (`traits/fold_fn_ghost`, `_noiter`, `_call`, and `_call_law`, the last discharging a call site by assuming an induction principle), and Map with the closure's precondition stated three ways (`traits/map_fn_uncond_pre` unconditionally, `traits/map_fn_concrete_item` at a concrete item type, and `traits/map_ext_total_pre` over the produced history with a preservation law). Each was run here after copying, all 24 files green. The remaining exploration on iterator-adapters is not adopted: `traits/map` and `traits/map_fn` are the superseded non-inductive Map invariant and still Unsat, and `traits/fold`, `traits/fold_fn`, `traits/fuse`, `traits/map_no_closure` and `traits/skip` have no fail twin to pin the other direction. Co-Authored-By: Claude Opus 5 (1M context) --- tests/ui/fail/ghost_seq_loop.rs | 34 +++ tests/ui/fail/ghost_seq_loop_forall.rs | 35 +++ tests/ui/fail/traits/fold_fn_ghost.rs | 86 +++++++ tests/ui/fail/traits/fold_fn_ghost_call.rs | 173 ++++++++++++++ .../ui/fail/traits/fold_fn_ghost_call_law.rs | 216 +++++++++++++++++ tests/ui/fail/traits/fold_fn_ghost_noiter.rs | 61 +++++ tests/ui/fail/traits/ghost_count.rs | 67 ++++++ tests/ui/fail/traits/ghost_produced.rs | 72 ++++++ tests/ui/fail/traits/ghost_step_chain.rs | 80 +++++++ tests/ui/fail/traits/map_ext_total_pre.rs | 215 +++++++++++++++++ tests/ui/fail/traits/map_fn_concrete_item.rs | 112 +++++++++ tests/ui/fail/traits/map_fn_uncond_pre.rs | 107 +++++++++ tests/ui/pass/ghost_seq_loop.rs | 34 +++ tests/ui/pass/ghost_seq_loop_forall.rs | 36 +++ tests/ui/pass/traits/fold_fn_ghost.rs | 87 +++++++ tests/ui/pass/traits/fold_fn_ghost_call.rs | 173 ++++++++++++++ .../ui/pass/traits/fold_fn_ghost_call_law.rs | 217 ++++++++++++++++++ tests/ui/pass/traits/fold_fn_ghost_noiter.rs | 62 +++++ tests/ui/pass/traits/ghost_count.rs | 67 ++++++ tests/ui/pass/traits/ghost_produced.rs | 72 ++++++ tests/ui/pass/traits/ghost_step_chain.rs | 80 +++++++ tests/ui/pass/traits/map_ext_total_pre.rs | 212 +++++++++++++++++ tests/ui/pass/traits/map_fn_concrete_item.rs | 119 ++++++++++ tests/ui/pass/traits/map_fn_uncond_pre.rs | 119 ++++++++++ 24 files changed, 2536 insertions(+) create mode 100644 tests/ui/fail/ghost_seq_loop.rs create mode 100644 tests/ui/fail/ghost_seq_loop_forall.rs create mode 100644 tests/ui/fail/traits/fold_fn_ghost.rs create mode 100644 tests/ui/fail/traits/fold_fn_ghost_call.rs create mode 100644 tests/ui/fail/traits/fold_fn_ghost_call_law.rs create mode 100644 tests/ui/fail/traits/fold_fn_ghost_noiter.rs create mode 100644 tests/ui/fail/traits/ghost_count.rs create mode 100644 tests/ui/fail/traits/ghost_produced.rs create mode 100644 tests/ui/fail/traits/ghost_step_chain.rs create mode 100644 tests/ui/fail/traits/map_ext_total_pre.rs create mode 100644 tests/ui/fail/traits/map_fn_concrete_item.rs create mode 100644 tests/ui/fail/traits/map_fn_uncond_pre.rs create mode 100644 tests/ui/pass/ghost_seq_loop.rs create mode 100644 tests/ui/pass/ghost_seq_loop_forall.rs create mode 100644 tests/ui/pass/traits/fold_fn_ghost.rs create mode 100644 tests/ui/pass/traits/fold_fn_ghost_call.rs create mode 100644 tests/ui/pass/traits/fold_fn_ghost_call_law.rs create mode 100644 tests/ui/pass/traits/fold_fn_ghost_noiter.rs create mode 100644 tests/ui/pass/traits/ghost_count.rs create mode 100644 tests/ui/pass/traits/ghost_produced.rs create mode 100644 tests/ui/pass/traits/ghost_step_chain.rs create mode 100644 tests/ui/pass/traits/map_ext_total_pre.rs create mode 100644 tests/ui/pass/traits/map_fn_concrete_item.rs create mode 100644 tests/ui/pass/traits/map_fn_uncond_pre.rs diff --git a/tests/ui/fail/ghost_seq_loop.rs b/tests/ui/fail/ghost_seq_loop.rs new file mode 100644 index 00000000..010f2ad6 --- /dev/null +++ b/tests/ui/fail/ghost_seq_loop.rs @@ -0,0 +1,34 @@ +//@error-in-other-file: Unsat +//@compile-flags: -C debug-assertions=off -A unused-variables +//@rustc-env: THRUST_SOLVER=tests/thrust-pcsat-wrapper COAR_IMAGE=coar:latest + +use thrust_models::model::{Int, Seq}; +use thrust_models::Ghost; + +struct Recorder { + count: i64, + hist: Ghost>, +} + +impl thrust_models::Model for Recorder { + type Ty = (Int, Seq); +} + +#[thrust_macros::context] +#[thrust_macros::requires((*r).0 == 0 && (*r).1.len() == 0 && n >= 0)] +#[thrust_macros::ensures((!r).1.len() == n)] +fn record_upto(r: &mut Recorder, n: i64) { + let rr = r; + let mut i = 0; + while i < n { + thrust_macros::invariant!( + |rr: &mut Recorder, r: thrust_models::FnParam<&mut Recorder>, i: i64, n: i64| + 0 <= i && i <= n && !rr == !r.at_entry() + ); + rr.hist = thrust_macros::ghost!(|rr: &mut Recorder, i: i64| -> Seq { (*rr).1.push(i) }); + rr.count += 1; + i += 1; + } +} + +fn main() {} diff --git a/tests/ui/fail/ghost_seq_loop_forall.rs b/tests/ui/fail/ghost_seq_loop_forall.rs new file mode 100644 index 00000000..4dcd2a69 --- /dev/null +++ b/tests/ui/fail/ghost_seq_loop_forall.rs @@ -0,0 +1,35 @@ +//@error-in-other-file: Unsat +//@compile-flags: -C debug-assertions=off -A unused-variables +//@rustc-env: THRUST_SOLVER=tests/thrust-pcsat-wrapper COAR_IMAGE=coar:latest + +use thrust_models::forall; +use thrust_models::model::{Int, Seq}; +use thrust_models::Ghost; + +struct Recorder { + count: i64, + hist: Ghost>, +} + +impl thrust_models::Model for Recorder { + type Ty = (Int, Seq); +} + +#[thrust_macros::context] +#[thrust_macros::requires((*r).0 == 0 && (*r).1.len() == 0 && n >= 0)] +#[thrust_macros::ensures(forall(|k: Int| 0 <= k && k < (!r).1.len() ==> (!r).1[k] < n))] +fn record_bounded(r: &mut Recorder, n: i64) { + let rr = r; + let mut i = 0; + while i < n { + thrust_macros::invariant!( + |rr: &mut Recorder, r: thrust_models::FnParam<&mut Recorder>, i: i64, n: i64| + 0 <= i && i <= n && (*rr).1.len() == i && !rr == !r.at_entry() + ); + rr.hist = thrust_macros::ghost!(|rr: &mut Recorder, i: i64| -> Seq { (*rr).1.push(i) }); + rr.count += 1; + i += 1; + } +} + +fn main() {} diff --git a/tests/ui/fail/traits/fold_fn_ghost.rs b/tests/ui/fail/traits/fold_fn_ghost.rs new file mode 100644 index 00000000..55387ea2 --- /dev/null +++ b/tests/ui/fail/traits/fold_fn_ghost.rs @@ -0,0 +1,86 @@ +//@error-in-other-file: Unsat +//@compile-flags: -C debug-assertions=off -A unused-variables +//@rustc-env: THRUST_SOLVER=tests/thrust-pcsat-wrapper THRUST_SOLVER_TIMEOUT_SECS=60 COAR_IMAGE=coar:latest + +use thrust_models::forall; +use thrust_models::model::{Int, Seq}; +use thrust_models::{Ghost, Model}; + +#[thrust_macros::context] +trait Iterator { + type Item; + + #[thrust_macros::requires(Self::invariant(*self))] + #[thrust_macros::ensures(Self::invariant(!self))] + #[thrust_macros::ensures(result == None ==> Self::completed(self))] + #[thrust_macros::ensures(forall(|i| result == Some(i) ==> Self::step(*self, i, !self)))] + fn next(&mut self) -> Option; + + #[thrust_macros::predicate] + fn invariant(self) -> bool; + #[thrust_macros::predicate] + fn completed(&mut self) -> bool; + #[thrust_macros::predicate] + fn step(self, item: Self::Item, dist: Self) -> bool; +} + +struct Run + Model> { + iter: I, + items: Ghost>, + accs: Ghost>, +} + +impl + Model> Model for Run { + type Ty = (::Ty, Seq, Seq); +} + +#[thrust_macros::context] +#[thrust_macros::requires( + I::invariant((*r).0) + && (*r).1.len() == 0 + && (*r).2.len() == 1 + && forall(|a: Int| forall(|x: Int| thrust_macros::pre!(f(a, x)))) +)] +#[thrust_macros::ensures( + result == (!r).2[(!r).1.len()] + && (!r).2.len() == (!r).1.len() + 1 + && forall(|k: Int| + 0 <= k && k < (!r).1.len() + ==> thrust_macros::post!(f((!r).2[k], (!r).1[k]), (!r).2[k + 1]) + ) +)] +fn fold + Model, F: Fn(i64, i64) -> i64>( + r: &mut Run, + init: i64, + f: F, +) -> i64 +where + ::Ty: PartialEq, +{ + let rr = r; + let mut acc = init; + let mut cnt = 0; + while let Some(x) = rr.iter.next() { + thrust_macros::invariant!( + |rr: &mut Run, r: thrust_models::FnParam<&mut Run>, f: F, acc: i64, cnt: i64| + I::invariant((*rr).0) + && !rr == !r.at_entry() + && 0 <= cnt + && (*rr).1.len() == cnt + && (*rr).2.len() == (*rr).1.len() + 1 + && acc == (*rr).2[(*rr).1.len()] + && forall(|a: Int| forall(|x: Int| thrust_macros::pre!(f(a, x)))) + && forall(|k: Int| + 0 <= k && k < (*rr).1.len() + ==> thrust_macros::post!(f((*rr).2[k], (*rr).1[k]), (*rr).2[k + 1]) + ) + ); + rr.items = thrust_macros::ghost!(|rr: &mut Run, x: i64| -> Seq { (*rr).1.push(x) }); + acc = f(acc, x); + rr.accs = thrust_macros::ghost!(|rr: &mut Run, acc: i64| -> Seq { (*rr).2.push(acc) }); + cnt += 1; + } + acc +} + +fn main() {} diff --git a/tests/ui/fail/traits/fold_fn_ghost_call.rs b/tests/ui/fail/traits/fold_fn_ghost_call.rs new file mode 100644 index 00000000..9ef4bff6 --- /dev/null +++ b/tests/ui/fail/traits/fold_fn_ghost_call.rs @@ -0,0 +1,173 @@ +//@error-in-other-file: Unsat +//@compile-flags: -C debug-assertions=off -A unused-variables +//@rustc-env: THRUST_SOLVER=tests/thrust-pcsat-wrapper THRUST_SOLVER_TIMEOUT_SECS=60 COAR_IMAGE=coar:latest + +use thrust_models::forall; +use thrust_models::model::{Int, Seq}; +use thrust_models::{Ghost, Model}; + +#[thrust_macros::context] +trait Iterator { + type Item; + + #[thrust_macros::requires(Self::invariant(*self))] + #[thrust_macros::ensures(Self::invariant(!self))] + #[thrust_macros::ensures(result == None ==> Self::completed(self))] + #[thrust_macros::ensures(forall(|i| result == Some(i) ==> Self::step(*self, i, !self)))] + fn next(&mut self) -> Option; + + #[thrust_macros::predicate] + fn invariant(self) -> bool; + #[thrust_macros::predicate] + fn completed(&mut self) -> bool; + #[thrust_macros::predicate] + fn step(self, item: Self::Item, dist: Self) -> bool; +} + +struct Run + Model> { + iter: I, + items: Ghost>, + accs: Ghost>, +} + +impl + Model> Model for Run { + type Ty = (::Ty, Seq, Seq); +} + +#[thrust_macros::context] +#[thrust_macros::requires( + I::invariant((*r).0) + && (*r).1.len() == 0 + && (*r).2.len() == 1 + && (*r).2[0] == init + && forall(|a: Int| forall(|x: Int| thrust_macros::pre!(f(a, x)))) +)] +#[thrust_macros::ensures( + result == (!r).2[(!r).1.len()] + && (!r).2.len() == (!r).1.len() + 1 + && forall(|k: Int| + 0 <= k && k < (!r).1.len() + ==> thrust_macros::post!(f((!r).2[k], (!r).1[k]), (!r).2[k + 1]) + ) +)] +fn fold + Model, F: Fn(i64, i64) -> i64>( + r: &mut Run, + init: i64, + f: F, +) -> i64 +where + ::Ty: PartialEq, +{ + let rr = r; + let mut acc = init; + let mut cnt = 0; + while let Some(x) = rr.iter.next() { + thrust_macros::invariant!( + |rr: &mut Run, r: thrust_models::FnParam<&mut Run>, f: F, acc: i64, cnt: i64| + I::invariant((*rr).0) + && !rr == !r.at_entry() + && 0 <= cnt + && (*rr).1.len() == cnt + && (*rr).2.len() == (*rr).1.len() + 1 + && acc == (*rr).2[(*rr).1.len()] + && forall(|a: Int| forall(|x: Int| thrust_macros::pre!(f(a, x)))) + && forall(|k: Int| + 0 <= k && k < (*rr).1.len() + ==> thrust_macros::post!(f((*rr).2[k], (*rr).1[k]), (*rr).2[k + 1]) + ) + ); + rr.items = thrust_macros::ghost!(|rr: &mut Run, x: i64| -> Seq { (*rr).1.push(x) }); + acc = f(acc, x); + rr.accs = thrust_macros::ghost!(|rr: &mut Run, acc: i64| -> Seq { (*rr).2.push(acc) }); + cnt += 1; + } + acc +} + +#[derive(PartialEq)] +struct Range { + start: i64, + end: i64, +} + +impl thrust_models::Model for Range { + type Ty = Range; +} + +#[thrust_macros::context] +impl Iterator for Range { + type Item = i64; + + fn next(&mut self) -> Option { + if self.start < self.end { + let item = self.start; + self.start += 1; + Some(item) + } else { + None + } + } + + #[thrust_macros::predicate] + fn invariant(self) -> bool { + "true"; + true + } + + #[thrust_macros::predicate] + fn completed(&mut self) -> bool { + "(and + (not (< + (tuple_proj.0 (mut_current> self_)) + (tuple_proj.1 (mut_current> self_)) + )) + (= (mut_current> self_) (mut_final> self_)) + )"; + true + } + + #[thrust_macros::predicate] + fn step(self, item: Self::Item, dist: Self) -> bool { + "(and + (= (tuple_proj.1 self_) (tuple_proj.1 dist)) + (= (tuple_proj.0 self_) item) + (= (+ (tuple_proj.0 self_) 1) (tuple_proj.0 dist)) + )"; + true + } +} + +// The call site: a concrete `Range`, folded with a total, unconditioned closure. +// The postcondition is a direct restatement of one conjunct of `fold`'s own ensures, +// so establishing it exercises only the call-site plumbing (requires discharge, +// ensures assumption), not any induction over the produced history. +#[thrust_macros::context] +#[thrust_macros::requires( + Range::invariant((*r).0) + && (*r).0.start == 0 && (*r).0.end == 3 + && (*r).1.len() == 0 + && (*r).2.len() == 1 + && (*r).2[0] == 0 +)] +#[thrust_macros::ensures( + result == (!r).2[(!r).1.len()] && (!r).2.len() == (!r).1.len() +)] +fn sum_range(r: &mut Run) -> i64 { + let f = thrust_macros::closure!( + requires(true), + ensures(result == a + x), + |a: i64, x: i64| -> i64 { a + x }, + ); + fold(r, 0, f) +} + +fn main() { + let range = Range { start: 0, end: 3 }; + let init: i64 = 0; + let mut r = Run { + iter: range, + items: thrust_macros::ghost!(|| -> Seq { Seq::empty() }), + accs: thrust_macros::ghost!(|init: i64| -> Seq { Seq::singleton(init) }), + }; + sum_range(&mut r); +} diff --git a/tests/ui/fail/traits/fold_fn_ghost_call_law.rs b/tests/ui/fail/traits/fold_fn_ghost_call_law.rs new file mode 100644 index 00000000..9271096c --- /dev/null +++ b/tests/ui/fail/traits/fold_fn_ghost_call_law.rs @@ -0,0 +1,216 @@ +//@error-in-other-file: Unsat +//@compile-flags: -C debug-assertions=off -A unused-variables +//@rustc-env: THRUST_SOLVER=tests/thrust-pcsat-wrapper THRUST_SOLVER_TIMEOUT_SECS=60 COAR_IMAGE=coar:latest + +use thrust_models::forall; +use thrust_models::model::{Int, Seq}; +use thrust_models::{Ghost, Model}; + +#[thrust_macros::context] +trait Iterator { + type Item; + + #[thrust_macros::requires(Self::invariant(*self))] + #[thrust_macros::ensures(Self::invariant(!self))] + #[thrust_macros::ensures(result == None ==> Self::completed(self))] + #[thrust_macros::ensures(forall(|i| result == Some(i) ==> Self::step(*self, i, !self)))] + fn next(&mut self) -> Option; + + #[thrust_macros::predicate] + fn invariant(self) -> bool; + #[thrust_macros::predicate] + fn completed(&mut self) -> bool; + #[thrust_macros::predicate] + fn step(self, item: Self::Item, dist: Self) -> bool; +} + +struct Run + Model> { + iter: I, + items: Ghost>, + accs: Ghost>, +} + +impl + Model> Model for Run { + type Ty = (::Ty, Seq, Seq); +} + +#[thrust_macros::context] +#[thrust_macros::requires( + I::invariant((*r).0) + && (*r).1.len() == 0 + && (*r).2.len() == 1 + && (*r).2[0] == init + && forall(|a: Int| forall(|x: Int| thrust_macros::pre!(f(a, x)))) +)] +#[thrust_macros::ensures( + result == (!r).2[(!r).1.len()] + && (!r).2.len() == (!r).1.len() + 1 + && forall(|k: Int| + 0 <= k && k < (!r).1.len() + ==> thrust_macros::post!(f((!r).2[k], (!r).1[k]), (!r).2[k + 1]) + ) +)] +fn fold + Model, F: Fn(i64, i64) -> i64>( + r: &mut Run, + init: i64, + f: F, +) -> i64 +where + ::Ty: PartialEq, +{ + let rr = r; + let mut acc = init; + let mut cnt = 0; + while let Some(x) = rr.iter.next() { + thrust_macros::invariant!( + |rr: &mut Run, r: thrust_models::FnParam<&mut Run>, f: F, acc: i64, cnt: i64, init: thrust_models::FnParam| + I::invariant((*rr).0) + && !rr == !r.at_entry() + && 0 <= cnt + && (*rr).1.len() == cnt + && (*rr).2.len() == (*rr).1.len() + 1 + && acc == (*rr).2[(*rr).1.len()] + && (*rr).2[0] == init.at_entry() + && forall(|a: Int| forall(|x: Int| thrust_macros::pre!(f(a, x)))) + && forall(|k: Int| + 0 <= k && k < (*rr).1.len() + ==> thrust_macros::post!(f((*rr).2[k], (*rr).1[k]), (*rr).2[k + 1]) + ) + ); + rr.items = thrust_macros::ghost!(|rr: &mut Run, x: i64| -> Seq { (*rr).1.push(x) }); + acc = f(acc, x); + rr.accs = thrust_macros::ghost!(|rr: &mut Run, acc: i64| -> Seq { (*rr).2.push(acc) }); + cnt += 1; + } + acc +} + +#[derive(PartialEq)] +struct Range { + start: i64, + end: i64, +} + +impl thrust_models::Model for Range { + type Ty = Range; +} + +#[thrust_macros::context] +impl Iterator for Range { + type Item = i64; + + fn next(&mut self) -> Option { + if self.start < self.end { + let item = self.start; + self.start += 1; + Some(item) + } else { + None + } + } + + #[thrust_macros::predicate] + fn invariant(self) -> bool { + "true"; + true + } + + #[thrust_macros::predicate] + fn completed(&mut self) -> bool { + "(and + (not (< + (tuple_proj.0 (mut_current> self_)) + (tuple_proj.1 (mut_current> self_)) + )) + (= (mut_current> self_) (mut_final> self_)) + )"; + true + } + + #[thrust_macros::predicate] + fn step(self, item: Self::Item, dist: Self) -> bool { + "(and + (= (tuple_proj.1 self_) (tuple_proj.1 dist)) + (= (tuple_proj.0 self_) item) + (= (+ (tuple_proj.0 self_) 1) (tuple_proj.0 dist)) + )"; + true + } +} + +// The call site: a concrete `Range`, folded with a total, unconditioned closure. +// The postcondition is a direct restatement of one conjunct of `fold`'s own ensures, +// so establishing it exercises only the call-site plumbing (requires discharge, +// ensures assumption), not any induction over the produced history. +#[thrust_macros::context] +#[thrust_macros::requires( + Range::invariant((*r).0) + && (*r).0.start == 0 && (*r).0.end == 3 + && (*r).1.len() == 0 + && (*r).2.len() == 1 + && (*r).2[0] == 0 +)] +#[thrust_macros::ensures( + result == (!r).2[(!r).1.len()] && (!r).2.len() == (!r).1.len() + 1 +)] +fn sum_range(r: &mut Run) -> i64 { + let f = thrust_macros::closure!( + requires(true), + ensures(result == a + x), + |a: i64, x: i64| -> i64 { a + x }, + ); + fold(r, 0, f) +} + +// An assumed induction principle over the accumulator history: a chain that never +// decreases ends no lower than it started. The step-wise premise is first order, but +// chaining it across a symbolic-length history is not, which is why this is assumed +// rather than derived. +#[thrust::trusted] +#[thrust_macros::context] +#[thrust_macros::requires( + (*r).2.len() == (*r).1.len() + 1 + && forall(|k: Int| 0 <= k && k < (*r).1.len() ==> (*r).2[k + 1] >= (*r).2[k]) +)] +#[thrust_macros::ensures((*r).2[(*r).1.len()] >= (*r).2[0])] +fn monotone_chain + Model>(r: &Run) {} + +// The call site that needs the induction: the result is at least the initial +// accumulator, which follows from the lemma plus the closure's own postcondition. +#[thrust_macros::context] +#[thrust_macros::requires( + Range::invariant((*r).0) + && (*r).0.start == 0 && (*r).0.end == 3 + && (*r).1.len() == 0 + && (*r).2.len() == 1 + && (*r).2[0] == 0 +)] +#[thrust_macros::ensures(result >= 0)] +fn sum_range_nonneg(r: &mut Run) -> i64 { + let f = thrust_macros::closure!( + requires(true), + ensures(result >= a), + |a: i64, x: i64| -> i64 { if x > 0 { a + x } else { a } }, + ); + let res = fold(&mut *r, 0, f); + monotone_chain(&*r); + res +} + +fn main() { + let range = Range { start: 0, end: 3 }; + let init: i64 = 0; + let mut r = Run { + iter: range, + items: thrust_macros::ghost!(|| -> Seq { Seq::empty() }), + accs: thrust_macros::ghost!(|init: i64| -> Seq { Seq::singleton(init) }), + }; + sum_range(&mut r); + + let mut r2 = Run { + iter: Range { start: 0, end: 3 }, + items: thrust_macros::ghost!(|| -> Seq { Seq::empty() }), + accs: thrust_macros::ghost!(|init: i64| -> Seq { Seq::singleton(init) }), + }; + sum_range_nonneg(&mut r2); +} diff --git a/tests/ui/fail/traits/fold_fn_ghost_noiter.rs b/tests/ui/fail/traits/fold_fn_ghost_noiter.rs new file mode 100644 index 00000000..4a034d6f --- /dev/null +++ b/tests/ui/fail/traits/fold_fn_ghost_noiter.rs @@ -0,0 +1,61 @@ +//@error-in-other-file: Unsat +//@compile-flags: -C debug-assertions=off -A unused-variables +//@rustc-env: THRUST_SOLVER=tests/thrust-pcsat-wrapper THRUST_SOLVER_TIMEOUT_SECS=60 COAR_IMAGE=coar:latest + +use thrust_models::forall; +use thrust_models::model::{Int, Seq}; +use thrust_models::Ghost; + +struct Run { + items: Ghost>, + accs: Ghost>, +} + +impl thrust_models::Model for Run { + type Ty = (Seq, Seq); +} + +// `Fn` rather than `FnMut`: the closure has no state to project, so the accumulator +// chain needs no prophecy, only the closure's own pre/postcondition. +#[thrust_macros::context] +#[thrust_macros::requires( + n >= 0 + && (*r).0.len() == 0 + && (*r).1.len() == 1 + && forall(|a: Int| forall(|x: Int| thrust_macros::pre!(f(a, x)))) +)] +#[thrust_macros::ensures( + result == (!r).1[(!r).0.len()] + && (!r).1.len() == (!r).0.len() + 1 + && forall(|k: Int| + 0 <= k && k < (!r).0.len() + ==> thrust_macros::post!(f((!r).1[k], (!r).0[k]), (!r).1[k + 1]) + ) +)] +fn fold_upto i64>(r: &mut Run, n: i64, init: i64, f: F) -> i64 { + let rr = r; + let mut acc = init; + let mut i = 0; + while i < n { + thrust_macros::invariant!( + |rr: &mut Run, r: thrust_models::FnParam<&mut Run>, f: F, acc: i64, i: i64, n: i64| + 0 <= i && i <= n + && !rr == !r.at_entry() + && (*rr).0.len() == i + && (*rr).1.len() == (*rr).0.len() + 1 + && acc == (*rr).1[(*rr).0.len()] + && forall(|a: Int| forall(|x: Int| thrust_macros::pre!(f(a, x)))) + && forall(|k: Int| + 0 <= k && k < (*rr).0.len() + ==> thrust_macros::post!(f((*rr).1[k], (*rr).0[k]), (*rr).1[k + 1]) + ) + ); + acc = f(acc, i); + rr.items = thrust_macros::ghost!(|rr: &mut Run, i: i64| -> Seq { (*rr).0.push(i) }); + rr.accs = thrust_macros::ghost!(|rr: &mut Run, acc: i64| -> Seq { (*rr).1.push(acc) }); + i += 1; + } + acc +} + +fn main() {} diff --git a/tests/ui/fail/traits/ghost_count.rs b/tests/ui/fail/traits/ghost_count.rs new file mode 100644 index 00000000..184d2004 --- /dev/null +++ b/tests/ui/fail/traits/ghost_count.rs @@ -0,0 +1,67 @@ +//@error-in-other-file: Unsat +//@compile-flags: -C debug-assertions=off -A unused-variables +//@rustc-env: THRUST_SOLVER=tests/thrust-pcsat-wrapper THRUST_SOLVER_TIMEOUT_SECS=60 COAR_IMAGE=coar:latest + +use thrust_models::forall; +use thrust_models::model::Seq; +use thrust_models::{Ghost, Model}; + +#[thrust_macros::context] +trait Iterator { + type Item; + + #[thrust_macros::requires(Self::invariant(*self))] + #[thrust_macros::ensures(Self::invariant(!self))] + #[thrust_macros::ensures(result == None ==> Self::completed(self))] + #[thrust_macros::ensures(forall(|i| result == Some(i) ==> Self::step(*self, i, !self)))] + fn next(&mut self) -> Option; + + #[thrust_macros::predicate] + fn invariant(self) -> bool; + #[thrust_macros::predicate] + fn completed(&mut self) -> bool; + #[thrust_macros::predicate] + fn step(self, item: Self::Item, dist: Self) -> bool; +} + +struct Run +where + I::Item: Model, +{ + iter: I, + items: Ghost::Ty>>, +} + +impl Model for Run +where + I::Item: Model, +{ + type Ty = (::Ty, Seq<::Ty>); +} + +// A fold whose accumulator is related to the produced history by the returned value. +#[thrust_macros::context] +#[thrust_macros::requires(I::invariant((*r).0))] +#[thrust_macros::ensures(result == (!r).1.len())] +fn count(r: &mut Run) -> i64 +where + I::Item: Model, + ::Ty: PartialEq, + ::Ty: PartialEq, +{ + let rr = r; + let mut acc = 0; + while let Some(x) = rr.iter.next() { + thrust_macros::invariant!( + |rr: &mut Run, r: thrust_models::FnParam<&mut Run>, acc: i64| + I::invariant((*rr).0) && !rr == !r.at_entry() && acc == (*rr).1.len() + ); + rr.items = thrust_macros::ghost!( + |rr: &mut Run, x: I::Item| -> Seq<::Ty> { (*rr).1.push(x) } + ); + acc += 1; + } + acc +} + +fn main() {} diff --git a/tests/ui/fail/traits/ghost_produced.rs b/tests/ui/fail/traits/ghost_produced.rs new file mode 100644 index 00000000..695a4e42 --- /dev/null +++ b/tests/ui/fail/traits/ghost_produced.rs @@ -0,0 +1,72 @@ +//@error-in-other-file: Unsat +//@compile-flags: -C debug-assertions=off -A unused-variables +//@rustc-env: THRUST_SOLVER=tests/thrust-pcsat-wrapper THRUST_SOLVER_TIMEOUT_SECS=60 COAR_IMAGE=coar:latest + +use thrust_models::forall; +use thrust_models::model::{Int, Seq}; +use thrust_models::{Ghost, Model}; + +#[thrust_macros::context] +trait Iterator { + type Item; + + #[thrust_macros::requires(Self::invariant(*self))] + #[thrust_macros::ensures(Self::invariant(!self))] + #[thrust_macros::ensures(result == None ==> Self::completed(self))] + #[thrust_macros::ensures(forall(|i| result == Some(i) ==> Self::step(*self, i, !self)))] + #[thrust_macros::ensures(forall(|i| result == Some(i) ==> Self::item_ok(i)))] + fn next(&mut self) -> Option; + + #[thrust_macros::predicate] + fn invariant(self) -> bool; + #[thrust_macros::predicate] + fn completed(&mut self) -> bool; + #[thrust_macros::predicate] + fn step(self, item: Self::Item, dist: Self) -> bool; + #[thrust_macros::predicate] + fn item_ok(item: Self::Item) -> bool; +} + +// The history of produced items lives in a ghost field, updated by the loop body, +// so the specification never has to existentially quantify it. +struct Run +where + I::Item: Model, +{ + iter: I, + produced: Ghost::Ty>>, +} + +impl Model for Run +where + I::Item: Model, +{ + type Ty = (::Ty, Seq<::Ty>); +} + +#[thrust_macros::context] +#[thrust_macros::requires(I::invariant((*r).0))] +#[thrust_macros::ensures( + forall(|k: Int| 0 <= k && k < (!r).1.len() ==> I::item_ok((!r).1[k])) +)] +fn drain(r: &mut Run) +where + I::Item: Model, + ::Ty: PartialEq, + ::Ty: PartialEq, +{ + let rr = r; + while let Some(x) = rr.iter.next() { + thrust_macros::invariant!( + |rr: &mut Run, r: thrust_models::FnParam<&mut Run>| + I::invariant((*rr).0) + && !rr == !r.at_entry() + && forall(|k: Int| 0 <= k && k < (*rr).1.len() ==> I::item_ok((*rr).1[k])) + ); + rr.produced = thrust_macros::ghost!( + |rr: &mut Run, x: I::Item| -> Seq<::Ty> { (*rr).1.push(x) } + ); + } +} + +fn main() {} diff --git a/tests/ui/fail/traits/ghost_step_chain.rs b/tests/ui/fail/traits/ghost_step_chain.rs new file mode 100644 index 00000000..00894e6b --- /dev/null +++ b/tests/ui/fail/traits/ghost_step_chain.rs @@ -0,0 +1,80 @@ +//@error-in-other-file: Unsat +//@compile-flags: -C debug-assertions=off -A unused-variables +//@rustc-env: THRUST_SOLVER=tests/thrust-pcsat-wrapper THRUST_SOLVER_TIMEOUT_SECS=60 COAR_IMAGE=coar:latest + +use thrust_models::forall; +use thrust_models::model::{Int, Seq}; +use thrust_models::{Ghost, Model}; + +#[thrust_macros::context] +trait Iterator { + type Item; + + #[thrust_macros::requires(Self::invariant(*self))] + #[thrust_macros::ensures(Self::invariant(!self))] + #[thrust_macros::ensures(result == None ==> Self::completed(self))] + #[thrust_macros::ensures(forall(|i| result == Some(i) ==> Self::step(*self, i, !self)))] + fn next(&mut self) -> Option; + + #[thrust_macros::predicate] + fn invariant(self) -> bool; + #[thrust_macros::predicate] + fn completed(&mut self) -> bool; + #[thrust_macros::predicate] + fn step(self, item: Self::Item, dist: Self) -> bool; +} + +// The state and item histories that fold's specification used to quantify +// existentially now live in ghost fields the loop body maintains. +struct Run +where + I::Item: Model, +{ + iter: I, + states: Ghost::Ty>>, + items: Ghost::Ty>>, +} + +impl Model for Run +where + I::Item: Model, +{ + type Ty = (::Ty, Seq<::Ty>, Seq<::Ty>); +} + +#[thrust_macros::context] +#[thrust_macros::requires( + I::invariant((*r).0) && (*r).1.len() == 1 && (*r).1[0] == (*r).0 +)] +#[thrust_macros::ensures( + forall(|k: Int| 0 <= k && k < (!r).2.len() ==> I::step((!r).1[k], (!r).2[k], (!r).1[k + 1])) +)] +fn drain_chain(r: &mut Run) +where + I::Item: Model, + ::Ty: PartialEq, + ::Ty: PartialEq, +{ + let rr = r; + while let Some(x) = rr.iter.next() { + thrust_macros::invariant!( + |rr: &mut Run, r: thrust_models::FnParam<&mut Run>| + I::invariant((*rr).0) + && !rr == !r.at_entry() + && (*rr).1.len() == (*rr).2.len() + 1 + && (*rr).1[(*rr).2.len()] == (*rr).0 + && forall(|k: Int| + 0 <= k && k < (*rr).2.len() + ==> I::step((*rr).1[k], (*rr).2[k], (*rr).1[k + 1]) + ) + ); + rr.items = thrust_macros::ghost!( + |rr: &mut Run, x: I::Item| -> Seq<::Ty> { (*rr).2.push(x) } + ); + rr.states = thrust_macros::ghost!( + |rr: &mut Run| -> Seq<::Ty> { (*rr).1.push((*rr).0) } + ); + } +} + +fn main() {} diff --git a/tests/ui/fail/traits/map_ext_total_pre.rs b/tests/ui/fail/traits/map_ext_total_pre.rs new file mode 100644 index 00000000..52af235b --- /dev/null +++ b/tests/ui/fail/traits/map_ext_total_pre.rs @@ -0,0 +1,215 @@ +//@error-in-other-file: Unsat +//@compile-flags: -C debug-assertions=off -A unused-variables +//@rustc-env: THRUST_SOLVER=tests/thrust-pcsat-wrapper THRUST_SOLVER_TIMEOUT_SECS=60 COAR_IMAGE=coar:latest +use thrust_models::forall; +use thrust_models::model::{Int, Seq}; +use thrust_models::{Ghost, Model}; + +#[thrust_macros::context] +trait Iterator { + type Item; + + #[thrust_macros::requires(Self::invariant(*self))] + #[thrust_macros::ensures(Self::invariant(!self))] + #[thrust_macros::ensures(result == None ==> Self::completed(self))] + #[thrust_macros::ensures(forall(|i| result == Some(i) ==> Self::step(*self, i, !self)))] + fn next(&mut self) -> Option; + + #[thrust_macros::predicate] + fn invariant(self) -> bool; + #[thrust_macros::predicate] + fn completed(&mut self) -> bool; + #[thrust_macros::predicate] + fn step(self, item: Self::Item, dist: Self) -> bool; +} + +// A variation on Creusot's `MapExt`, not `MapExt` itself -- the difference is +// in the second paragraph. The closure receives the ghost history of items +// produced so far (`Ghost>`), so its precondition can depend on what +// has come before instead of being stated unconditionally over the whole item +// type (contrast `map_fn_uncond_pre.rs`). The history lives in a ghost `produced` +// field updated by `next` itself -- no existential witness array in `step`. +// +// The precondition here is conditioned on the history alone, not on what the +// inner iterator can still actually produce (Creusot's `next_precondition`, +// which quantifies over `self.iter.produces(...)`): that reachability- +// conditioned form was tried first and is NOT inductive on its own (see the +// commit report) -- Creusot pairs it with a `preservation` law justified by +// `produces_trans`, untried here. What DOES verify is `preservation` stated as +// a fact about the closure alone, quantified over an arbitrary history: for +// ANY history, if the closure accepted one more item, its precondition still +// holds for ANY next item at the extended history. That is enough to make the +// per-position "precondition holds for the current history" conjunct +// inductive, without needing `produces`/`produces_refl`/`produces_trans` at +// all -- at the cost of requiring the precondition to hold for every possible +// next item, not just producible ones. +struct Map { + iter: I, + func: F, + produced: Ghost>, +} + +impl Model for Map { + type Ty = Map; +} + +// Obstacle: a `Ghost`-typed FIELD has no model-level accessor in a `ghost!` +// body. `Map`'s model is the struct itself, so `s.produced` stays +// `Ghost>` there and `push` is not found (E0599); reaching it through +// a `&mut Map` the way `fold_fn_ghost_call_law.rs` reaches `Run`'s tuple model +// is not available. A `Ghost` PARAMETER is modelled as `T`, so the push has +// to happen in a function that takes one. Binding the field to a local first +// does not help either -- the ghost term then reports the item as not live. +// +// This is NOT the `Self`-in-a-generic-trait-impl gap that forall-sort c0cfee5 +// fixed; `ghost_in_generic_impl.rs` shows `ghost!` working directly in such an +// impl now. Only the field access keeps this workaround. +#[thrust_macros::ensures(result == produced.push(x))] +fn push_produced(produced: Ghost>, x: i64) -> Ghost> { + thrust_macros::ghost!(|produced: Ghost>, x: i64| -> Seq { produced.push(x) }) +} + +#[thrust_macros::context] +impl + Model, F: Fn(i64, Ghost>) -> i64> Iterator for Map +where + ::Ty: PartialEq, +{ + type Item = i64; + + fn next(&mut self) -> Option { + match self.iter.next() { + Some(v) => { + let r = (self.func)(v, self.produced); + self.produced = push_produced(self.produced, v); + Some(r) + } + None => None, + } + } + + #[thrust_macros::predicate] + fn invariant(self) -> bool { + // self.iter.invariant() && + // forall(|e: i64| pre!(self.func(e, self.produced))) && + // preservation(self.func): forall(|harr, hlen, e1: i64, e2: i64, b: i64| + // pre!(self.func(e1, (harr,hlen))) && post!(self.func(e1, (harr,hlen)), b) + // ==> pre!(self.func(e2, (harr,hlen).push(e1)))) + // + // Obstacle found and worked around here: quantifying with `forall` over + // a variable of the packed `Seq`-model TUPLE sort + // (`Tuple-Int>`) crashes COAR's SMT-LIB2 parser + // (`Failure " is already bound"`, independent of the chosen bound + // name -- confirmed with several). Workaround: quantify over the + // tuple's own FIELDS (an `Array Int Int` and an `Int` length) and + // reconstruct the tuple inline via the `tuple<...>` constructor. + // Break: drop the `self.iter.invariant()` conjunct -- `next`'s own + // `requires(Self::invariant(*self))` no longer implies the inner + // iterator's precondition, so `self.iter.next()` cannot be called. + "(and + true + (forall ((e Int)) + (q_pre_next_bedbd733d3f248d989e85efaa8d1bc7 + (tuple_proj-Int>>.1 self_) + e + (tuple_proj-Int>>.2 self_) + ) + ) + (forall ((harr (Array Int Int)) (hlen Int)) + (forall ((e1 Int)) + (forall ((e2 Int)) + (forall ((b Int)) + (=> + (and + (q_pre_next_bedbd733d3f248d989e85efaa8d1bc7 + (tuple_proj-Int>>.1 self_) + e1 + (tuple-Int> harr hlen) + ) + (q_post_next_bedbd733d3f248d989e85efaa8d1bc7 + (tuple_proj-Int>>.1 self_) + e1 + (tuple-Int> harr hlen) + b + ) + ) + (q_pre_next_bedbd733d3f248d989e85efaa8d1bc7 + (tuple_proj-Int>>.1 self_) + e2 + (tuple-Int> (store harr hlen e1) (+ hlen 1)) + ) + ) + ) + ) + ) + ) + )"; + true + } + + #[thrust_macros::predicate] + fn completed(&mut self) -> bool { + // self.iter.completed() && *self.func == !self.func && *self.produced == !self.produced + "(and + (q_completed_bedbd733d3f248d6f3ca13bf4a6f7f6 + (mut + (tuple_proj-Int>>.0 (mut_current-Int>>> self_)) + (tuple_proj-Int>>.0 (mut_final-Int>>> self_)) + ) + ) + (= + (tuple_proj-Int>>.1 (mut_current-Int>>> self_)) + (tuple_proj-Int>>.1 (mut_final-Int>>> self_)) + ) + (= + (tuple_proj-Int>>.2 (mut_current-Int>>> self_)) + (tuple_proj-Int>>.2 (mut_final-Int>>> self_)) + ) + )"; + true + } + + #[thrust_macros::predicate] + fn step(self, item: Self::Item, dist: Self) -> bool { + // exists(|i: i64| self.iter.step(i, dist.iter)) && + // pre!(self.func(i, self.produced)) && post!(self.func(i, self.produced), item) && + // self.func == dist.func && dist.produced == self.produced.push(i) + "(exists ((i Int)) + (and + (q_step_bedbd733d3f248d84d555206bfaa09e + (tuple_proj-Int>>.0 self_) + i + (tuple_proj-Int>>.0 dist) + ) + (q_pre_next_bedbd733d3f248d989e85efaa8d1bc7 + (tuple_proj-Int>>.1 self_) + i + (tuple_proj-Int>>.2 self_) + ) + (q_post_next_bedbd733d3f248d989e85efaa8d1bc7 + (tuple_proj-Int>>.1 self_) + i + (tuple_proj-Int>>.2 self_) + item + ) + (= + (tuple_proj-Int>>.1 self_) + (tuple_proj-Int>>.1 dist) + ) + (= + (tuple_proj-Int>>.2 dist) + (tuple-Int> + (store + (tuple_proj-Int>.0 (tuple_proj-Int>>.2 self_)) + (tuple_proj-Int>.1 (tuple_proj-Int>>.2 self_)) + i + ) + (+ (tuple_proj-Int>.1 (tuple_proj-Int>>.2 self_)) 1) + ) + ) + ) + )"; + true + } +} + +fn main() {} diff --git a/tests/ui/fail/traits/map_fn_concrete_item.rs b/tests/ui/fail/traits/map_fn_concrete_item.rs new file mode 100644 index 00000000..ccecb65c --- /dev/null +++ b/tests/ui/fail/traits/map_fn_concrete_item.rs @@ -0,0 +1,112 @@ +//@error-in-other-file: Unsat +//@compile-flags: -C debug-assertions=off -A unused-variables +//@rustc-env: THRUST_SOLVER=tests/thrust-pcsat-wrapper THRUST_SOLVER_TIMEOUT_SECS=60 COAR_IMAGE=coar:latest +use thrust_models::forall; + +#[thrust_macros::context] +trait Iterator { + type Item; + + #[thrust_macros::requires(Self::invariant(*self))] + #[thrust_macros::ensures(Self::invariant(!self))] + #[thrust_macros::ensures(result == None ==> Self::completed(self))] + #[thrust_macros::ensures(forall(|i| result == Some(i) ==> Self::step(*self, i, !self)))] + fn next(&mut self) -> Option; + + #[thrust_macros::predicate] + fn invariant(self) -> bool; + #[thrust_macros::predicate] + fn completed(&mut self) -> bool; + #[thrust_macros::predicate] + fn step(self, item: Self::Item, dist: Self) -> bool; +} + +struct Map { + // The inner iterator + iter: I, + // The mapper + func: F, +} + +impl thrust_models::Model for Map { + type Ty = Map; +} + +// `Map`'s own struct-level type arguments are `` -- fixing the closure's +// output to a concrete `i64` (rather than an impl-only `B: Model` that never +// appears in `Map`'s own type arguments) removes the type parameter that +// panicked when a call site tried to resolve it. +#[thrust_macros::context] +impl + thrust_models::Model, F: Fn(i64) -> i64> Iterator for Map +where + ::Ty: PartialEq, +{ + type Item = i64; + + fn next(&mut self) -> Option { + match self.iter.next() { + Some(v) => { + Some((self.func)(v)) + } + None => None, + } + } + + #[thrust_macros::predicate] + fn invariant(self) -> bool { + // self.iter.invariant() + // The `forall(|i| pre!(self.func(i)))` conjunct is dropped here, so nothing + // establishes the closure's precondition before `next`'s body calls it. + "(q_invariant_4d8c188fb84596fee9a2d9c5fc98ae25 (tuple_proj.0 self_))"; + true + } + + #[thrust_macros::predicate] + fn completed(&mut self) -> bool { + // self.iter.completed() && *self.func == !self.func + "(and + (q_completed_4d8c188fb84596fe73c306e4bc6f95ef + (mut + (tuple_proj.0 (mut_current> self_)) + (tuple_proj.0 (mut_final> self_)) + ) + ) + (= + (tuple_proj.1 (mut_current> self_)) + (tuple_proj.1 (mut_final> self_)) + ) + )"; + true + } + + #[thrust_macros::predicate] + fn step(self, item: Self::Item, dist: Self) -> bool { + // exists(|i: i64| self.iter.step(i, dist.iter)) && + // pre!(self.func(i)) && post!(self.func(i), item) && self.func == dist.func + "(exists ((i Int)) + (and + (q_step_4d8c188fb84596fe3fcc020be02ea8ac + (tuple_proj.0 self_) + i + (tuple_proj.0 dist) + ) + (q_pre_next_4d8c188fb84596fecd3dcc87543efe66 + (tuple_proj.1 self_) + i + ) + (q_post_next_4d8c188fb84596fecd3dcc87543efe66 + (tuple_proj.1 self_) + i + item + ) + (= + (tuple_proj.1 self_) + (tuple_proj.1 dist) + ) + ) + )"; + true + } +} + +fn main() {} diff --git a/tests/ui/fail/traits/map_fn_uncond_pre.rs b/tests/ui/fail/traits/map_fn_uncond_pre.rs new file mode 100644 index 00000000..77085fc8 --- /dev/null +++ b/tests/ui/fail/traits/map_fn_uncond_pre.rs @@ -0,0 +1,107 @@ +//@error-in-other-file: Unsat +//@compile-flags: -C debug-assertions=off -A unused-variables +//@rustc-env: THRUST_SOLVER=tests/thrust-pcsat-wrapper THRUST_SOLVER_TIMEOUT_SECS=60 COAR_IMAGE=coar:latest +use thrust_models::forall; + +#[thrust_macros::context] +trait Iterator { + type Item; + + #[thrust_macros::requires(Self::invariant(*self))] + #[thrust_macros::ensures(Self::invariant(!self))] + #[thrust_macros::ensures(result == None ==> Self::completed(self))] + #[thrust_macros::ensures(forall(|i| result == Some(i) ==> Self::step(*self, i, !self)))] + fn next(&mut self) -> Option; + + #[thrust_macros::predicate] + fn invariant(self) -> bool; + #[thrust_macros::predicate] + fn completed(&mut self) -> bool; + #[thrust_macros::predicate] + fn step(self, item: Self::Item, dist: Self) -> bool; +} + +struct Map { + // The inner iterator + iter: I, + // The mapper + func: F, +} + +impl thrust_models::Model for Map { + type Ty = Map; +} + +#[thrust_macros::context] +impl B> Iterator for Map +where ::Ty: PartialEq +{ + type Item = B; + + fn next(&mut self) -> Option { + match self.iter.next() { + Some(v) => { + Some((self.func)(v)) + } + None => None, + } + } + + #[thrust_macros::predicate] + fn invariant(self) -> bool { + // self.iter.invariant() + // The `forall(|i| pre!(self.func(i)))` conjunct is dropped here, so nothing + // establishes the closure's precondition before `next`'s body calls it. + "(q_invariant_597ac4b22488a2bc34d254b9ac53a96e (tuple_proj.0 self_))"; + true + } + + #[thrust_macros::predicate] + fn completed(&mut self) -> bool { + // self.iter.completed() && *self.func == !self.func + "(and + (q_completed_597ac4b22488a2bcd79190db0c73456e + (mut + (tuple_proj.0 (mut_current> self_)) + (tuple_proj.0 (mut_final> self_)) + ) + ) + (= + (tuple_proj.1 (mut_current> self_)) + (tuple_proj.1 (mut_final> self_)) + ) + )"; + true + } + + #[thrust_macros::predicate] + fn step(self, item: Self::Item, dist: Self) -> bool { + // exists(|i: Self::Item| self.iter.step(i, dist.iter)) && + // pre!(self.func(i)) && post!(self.func(i), item) && self.func == dist.func + "(exists ((i a3)) + (and + (q_step_597ac4b22488a2bc6c728c715e62f635 + (tuple_proj.0 self_) + i + (tuple_proj.0 dist) + ) + (q_pre_next_597ac4b22488a2bc3015b2bc3056d418 + (tuple_proj.1 self_) + i + ) + (q_post_next_597ac4b22488a2bc3015b2bc3056d418 + (tuple_proj.1 self_) + i + item + ) + (= + (tuple_proj.1 self_) + (tuple_proj.1 dist) + ) + ) + )"; + true + } +} + +fn main() {} diff --git a/tests/ui/pass/ghost_seq_loop.rs b/tests/ui/pass/ghost_seq_loop.rs new file mode 100644 index 00000000..501a98c0 --- /dev/null +++ b/tests/ui/pass/ghost_seq_loop.rs @@ -0,0 +1,34 @@ +//@check-pass +//@compile-flags: -C debug-assertions=off -A unused-variables +//@rustc-env: THRUST_SOLVER=tests/thrust-pcsat-wrapper COAR_IMAGE=coar:latest + +use thrust_models::model::{Int, Seq}; +use thrust_models::Ghost; + +struct Recorder { + count: i64, + hist: Ghost>, +} + +impl thrust_models::Model for Recorder { + type Ty = (Int, Seq); +} + +#[thrust_macros::context] +#[thrust_macros::requires((*r).0 == 0 && (*r).1.len() == 0 && n >= 0)] +#[thrust_macros::ensures((!r).1.len() == n)] +fn record_upto(r: &mut Recorder, n: i64) { + let rr = r; + let mut i = 0; + while i < n { + thrust_macros::invariant!( + |rr: &mut Recorder, r: thrust_models::FnParam<&mut Recorder>, i: i64, n: i64| + 0 <= i && i <= n && (*rr).1.len() == i && !rr == !r.at_entry() + ); + rr.hist = thrust_macros::ghost!(|rr: &mut Recorder, i: i64| -> Seq { (*rr).1.push(i) }); + rr.count += 1; + i += 1; + } +} + +fn main() {} diff --git a/tests/ui/pass/ghost_seq_loop_forall.rs b/tests/ui/pass/ghost_seq_loop_forall.rs new file mode 100644 index 00000000..a120b5bf --- /dev/null +++ b/tests/ui/pass/ghost_seq_loop_forall.rs @@ -0,0 +1,36 @@ +//@check-pass +//@compile-flags: -C debug-assertions=off -A unused-variables +//@rustc-env: THRUST_SOLVER=tests/thrust-pcsat-wrapper COAR_IMAGE=coar:latest + +use thrust_models::forall; +use thrust_models::model::{Int, Seq}; +use thrust_models::Ghost; + +struct Recorder { + count: i64, + hist: Ghost>, +} + +impl thrust_models::Model for Recorder { + type Ty = (Int, Seq); +} + +#[thrust_macros::context] +#[thrust_macros::requires((*r).0 == 0 && (*r).1.len() == 0 && n >= 0)] +#[thrust_macros::ensures(forall(|k: Int| 0 <= k && k < (!r).1.len() ==> (!r).1[k] < n))] +fn record_bounded(r: &mut Recorder, n: i64) { + let rr = r; + let mut i = 0; + while i < n { + thrust_macros::invariant!( + |rr: &mut Recorder, r: thrust_models::FnParam<&mut Recorder>, i: i64, n: i64| + 0 <= i && i <= n && (*rr).1.len() == i && !rr == !r.at_entry() + && forall(|k: Int| 0 <= k && k < (*rr).1.len() ==> (*rr).1[k] < n) + ); + rr.hist = thrust_macros::ghost!(|rr: &mut Recorder, i: i64| -> Seq { (*rr).1.push(i) }); + rr.count += 1; + i += 1; + } +} + +fn main() {} diff --git a/tests/ui/pass/traits/fold_fn_ghost.rs b/tests/ui/pass/traits/fold_fn_ghost.rs new file mode 100644 index 00000000..021508bf --- /dev/null +++ b/tests/ui/pass/traits/fold_fn_ghost.rs @@ -0,0 +1,87 @@ +//@check-pass +//@compile-flags: -C debug-assertions=off -A unused-variables +//@rustc-env: THRUST_SOLVER=tests/thrust-pcsat-wrapper THRUST_SOLVER_TIMEOUT_SECS=60 COAR_IMAGE=coar:latest + +use thrust_models::forall; +use thrust_models::model::{Int, Seq}; +use thrust_models::{Ghost, Model}; + +#[thrust_macros::context] +trait Iterator { + type Item; + + #[thrust_macros::requires(Self::invariant(*self))] + #[thrust_macros::ensures(Self::invariant(!self))] + #[thrust_macros::ensures(result == None ==> Self::completed(self))] + #[thrust_macros::ensures(forall(|i| result == Some(i) ==> Self::step(*self, i, !self)))] + fn next(&mut self) -> Option; + + #[thrust_macros::predicate] + fn invariant(self) -> bool; + #[thrust_macros::predicate] + fn completed(&mut self) -> bool; + #[thrust_macros::predicate] + fn step(self, item: Self::Item, dist: Self) -> bool; +} + +struct Run + Model> { + iter: I, + items: Ghost>, + accs: Ghost>, +} + +impl + Model> Model for Run { + type Ty = (::Ty, Seq, Seq); +} + +#[thrust_macros::context] +#[thrust_macros::requires( + I::invariant((*r).0) + && (*r).1.len() == 0 + && (*r).2.len() == 1 + && (*r).2[0] == init + && forall(|a: Int| forall(|x: Int| thrust_macros::pre!(f(a, x)))) +)] +#[thrust_macros::ensures( + result == (!r).2[(!r).1.len()] + && (!r).2.len() == (!r).1.len() + 1 + && forall(|k: Int| + 0 <= k && k < (!r).1.len() + ==> thrust_macros::post!(f((!r).2[k], (!r).1[k]), (!r).2[k + 1]) + ) +)] +fn fold + Model, F: Fn(i64, i64) -> i64>( + r: &mut Run, + init: i64, + f: F, +) -> i64 +where + ::Ty: PartialEq, +{ + let rr = r; + let mut acc = init; + let mut cnt = 0; + while let Some(x) = rr.iter.next() { + thrust_macros::invariant!( + |rr: &mut Run, r: thrust_models::FnParam<&mut Run>, f: F, acc: i64, cnt: i64| + I::invariant((*rr).0) + && !rr == !r.at_entry() + && 0 <= cnt + && (*rr).1.len() == cnt + && (*rr).2.len() == (*rr).1.len() + 1 + && acc == (*rr).2[(*rr).1.len()] + && forall(|a: Int| forall(|x: Int| thrust_macros::pre!(f(a, x)))) + && forall(|k: Int| + 0 <= k && k < (*rr).1.len() + ==> thrust_macros::post!(f((*rr).2[k], (*rr).1[k]), (*rr).2[k + 1]) + ) + ); + rr.items = thrust_macros::ghost!(|rr: &mut Run, x: i64| -> Seq { (*rr).1.push(x) }); + acc = f(acc, x); + rr.accs = thrust_macros::ghost!(|rr: &mut Run, acc: i64| -> Seq { (*rr).2.push(acc) }); + cnt += 1; + } + acc +} + +fn main() {} diff --git a/tests/ui/pass/traits/fold_fn_ghost_call.rs b/tests/ui/pass/traits/fold_fn_ghost_call.rs new file mode 100644 index 00000000..5ba1df4c --- /dev/null +++ b/tests/ui/pass/traits/fold_fn_ghost_call.rs @@ -0,0 +1,173 @@ +//@check-pass +//@compile-flags: -C debug-assertions=off -A unused-variables +//@rustc-env: THRUST_SOLVER=tests/thrust-pcsat-wrapper THRUST_SOLVER_TIMEOUT_SECS=60 COAR_IMAGE=coar:latest + +use thrust_models::forall; +use thrust_models::model::{Int, Seq}; +use thrust_models::{Ghost, Model}; + +#[thrust_macros::context] +trait Iterator { + type Item; + + #[thrust_macros::requires(Self::invariant(*self))] + #[thrust_macros::ensures(Self::invariant(!self))] + #[thrust_macros::ensures(result == None ==> Self::completed(self))] + #[thrust_macros::ensures(forall(|i| result == Some(i) ==> Self::step(*self, i, !self)))] + fn next(&mut self) -> Option; + + #[thrust_macros::predicate] + fn invariant(self) -> bool; + #[thrust_macros::predicate] + fn completed(&mut self) -> bool; + #[thrust_macros::predicate] + fn step(self, item: Self::Item, dist: Self) -> bool; +} + +struct Run + Model> { + iter: I, + items: Ghost>, + accs: Ghost>, +} + +impl + Model> Model for Run { + type Ty = (::Ty, Seq, Seq); +} + +#[thrust_macros::context] +#[thrust_macros::requires( + I::invariant((*r).0) + && (*r).1.len() == 0 + && (*r).2.len() == 1 + && (*r).2[0] == init + && forall(|a: Int| forall(|x: Int| thrust_macros::pre!(f(a, x)))) +)] +#[thrust_macros::ensures( + result == (!r).2[(!r).1.len()] + && (!r).2.len() == (!r).1.len() + 1 + && forall(|k: Int| + 0 <= k && k < (!r).1.len() + ==> thrust_macros::post!(f((!r).2[k], (!r).1[k]), (!r).2[k + 1]) + ) +)] +fn fold + Model, F: Fn(i64, i64) -> i64>( + r: &mut Run, + init: i64, + f: F, +) -> i64 +where + ::Ty: PartialEq, +{ + let rr = r; + let mut acc = init; + let mut cnt = 0; + while let Some(x) = rr.iter.next() { + thrust_macros::invariant!( + |rr: &mut Run, r: thrust_models::FnParam<&mut Run>, f: F, acc: i64, cnt: i64| + I::invariant((*rr).0) + && !rr == !r.at_entry() + && 0 <= cnt + && (*rr).1.len() == cnt + && (*rr).2.len() == (*rr).1.len() + 1 + && acc == (*rr).2[(*rr).1.len()] + && forall(|a: Int| forall(|x: Int| thrust_macros::pre!(f(a, x)))) + && forall(|k: Int| + 0 <= k && k < (*rr).1.len() + ==> thrust_macros::post!(f((*rr).2[k], (*rr).1[k]), (*rr).2[k + 1]) + ) + ); + rr.items = thrust_macros::ghost!(|rr: &mut Run, x: i64| -> Seq { (*rr).1.push(x) }); + acc = f(acc, x); + rr.accs = thrust_macros::ghost!(|rr: &mut Run, acc: i64| -> Seq { (*rr).2.push(acc) }); + cnt += 1; + } + acc +} + +#[derive(PartialEq)] +struct Range { + start: i64, + end: i64, +} + +impl thrust_models::Model for Range { + type Ty = Range; +} + +#[thrust_macros::context] +impl Iterator for Range { + type Item = i64; + + fn next(&mut self) -> Option { + if self.start < self.end { + let item = self.start; + self.start += 1; + Some(item) + } else { + None + } + } + + #[thrust_macros::predicate] + fn invariant(self) -> bool { + "true"; + true + } + + #[thrust_macros::predicate] + fn completed(&mut self) -> bool { + "(and + (not (< + (tuple_proj.0 (mut_current> self_)) + (tuple_proj.1 (mut_current> self_)) + )) + (= (mut_current> self_) (mut_final> self_)) + )"; + true + } + + #[thrust_macros::predicate] + fn step(self, item: Self::Item, dist: Self) -> bool { + "(and + (= (tuple_proj.1 self_) (tuple_proj.1 dist)) + (= (tuple_proj.0 self_) item) + (= (+ (tuple_proj.0 self_) 1) (tuple_proj.0 dist)) + )"; + true + } +} + +// The call site: a concrete `Range`, folded with a total, unconditioned closure. +// The postcondition is a direct restatement of one conjunct of `fold`'s own ensures, +// so establishing it exercises only the call-site plumbing (requires discharge, +// ensures assumption), not any induction over the produced history. +#[thrust_macros::context] +#[thrust_macros::requires( + Range::invariant((*r).0) + && (*r).0.start == 0 && (*r).0.end == 3 + && (*r).1.len() == 0 + && (*r).2.len() == 1 + && (*r).2[0] == 0 +)] +#[thrust_macros::ensures( + result == (!r).2[(!r).1.len()] && (!r).2.len() == (!r).1.len() + 1 +)] +fn sum_range(r: &mut Run) -> i64 { + let f = thrust_macros::closure!( + requires(true), + ensures(result == a + x), + |a: i64, x: i64| -> i64 { a + x }, + ); + fold(r, 0, f) +} + +fn main() { + let range = Range { start: 0, end: 3 }; + let init: i64 = 0; + let mut r = Run { + iter: range, + items: thrust_macros::ghost!(|| -> Seq { Seq::empty() }), + accs: thrust_macros::ghost!(|init: i64| -> Seq { Seq::singleton(init) }), + }; + sum_range(&mut r); +} diff --git a/tests/ui/pass/traits/fold_fn_ghost_call_law.rs b/tests/ui/pass/traits/fold_fn_ghost_call_law.rs new file mode 100644 index 00000000..850cde8c --- /dev/null +++ b/tests/ui/pass/traits/fold_fn_ghost_call_law.rs @@ -0,0 +1,217 @@ +//@check-pass +//@compile-flags: -C debug-assertions=off -A unused-variables +//@rustc-env: THRUST_SOLVER=tests/thrust-pcsat-wrapper THRUST_SOLVER_TIMEOUT_SECS=60 COAR_IMAGE=coar:latest + +use thrust_models::forall; +use thrust_models::model::{Int, Seq}; +use thrust_models::{Ghost, Model}; + +#[thrust_macros::context] +trait Iterator { + type Item; + + #[thrust_macros::requires(Self::invariant(*self))] + #[thrust_macros::ensures(Self::invariant(!self))] + #[thrust_macros::ensures(result == None ==> Self::completed(self))] + #[thrust_macros::ensures(forall(|i| result == Some(i) ==> Self::step(*self, i, !self)))] + fn next(&mut self) -> Option; + + #[thrust_macros::predicate] + fn invariant(self) -> bool; + #[thrust_macros::predicate] + fn completed(&mut self) -> bool; + #[thrust_macros::predicate] + fn step(self, item: Self::Item, dist: Self) -> bool; +} + +struct Run + Model> { + iter: I, + items: Ghost>, + accs: Ghost>, +} + +impl + Model> Model for Run { + type Ty = (::Ty, Seq, Seq); +} + +#[thrust_macros::context] +#[thrust_macros::requires( + I::invariant((*r).0) + && (*r).1.len() == 0 + && (*r).2.len() == 1 + && (*r).2[0] == init + && forall(|a: Int| forall(|x: Int| thrust_macros::pre!(f(a, x)))) +)] +#[thrust_macros::ensures( + result == (!r).2[(!r).1.len()] + && (!r).2.len() == (!r).1.len() + 1 + && (!r).2[0] == init + && forall(|k: Int| + 0 <= k && k < (!r).1.len() + ==> thrust_macros::post!(f((!r).2[k], (!r).1[k]), (!r).2[k + 1]) + ) +)] +fn fold + Model, F: Fn(i64, i64) -> i64>( + r: &mut Run, + init: i64, + f: F, +) -> i64 +where + ::Ty: PartialEq, +{ + let rr = r; + let mut acc = init; + let mut cnt = 0; + while let Some(x) = rr.iter.next() { + thrust_macros::invariant!( + |rr: &mut Run, r: thrust_models::FnParam<&mut Run>, f: F, acc: i64, cnt: i64, init: thrust_models::FnParam| + I::invariant((*rr).0) + && !rr == !r.at_entry() + && 0 <= cnt + && (*rr).1.len() == cnt + && (*rr).2.len() == (*rr).1.len() + 1 + && acc == (*rr).2[(*rr).1.len()] + && (*rr).2[0] == init.at_entry() + && forall(|a: Int| forall(|x: Int| thrust_macros::pre!(f(a, x)))) + && forall(|k: Int| + 0 <= k && k < (*rr).1.len() + ==> thrust_macros::post!(f((*rr).2[k], (*rr).1[k]), (*rr).2[k + 1]) + ) + ); + rr.items = thrust_macros::ghost!(|rr: &mut Run, x: i64| -> Seq { (*rr).1.push(x) }); + acc = f(acc, x); + rr.accs = thrust_macros::ghost!(|rr: &mut Run, acc: i64| -> Seq { (*rr).2.push(acc) }); + cnt += 1; + } + acc +} + +#[derive(PartialEq)] +struct Range { + start: i64, + end: i64, +} + +impl thrust_models::Model for Range { + type Ty = Range; +} + +#[thrust_macros::context] +impl Iterator for Range { + type Item = i64; + + fn next(&mut self) -> Option { + if self.start < self.end { + let item = self.start; + self.start += 1; + Some(item) + } else { + None + } + } + + #[thrust_macros::predicate] + fn invariant(self) -> bool { + "true"; + true + } + + #[thrust_macros::predicate] + fn completed(&mut self) -> bool { + "(and + (not (< + (tuple_proj.0 (mut_current> self_)) + (tuple_proj.1 (mut_current> self_)) + )) + (= (mut_current> self_) (mut_final> self_)) + )"; + true + } + + #[thrust_macros::predicate] + fn step(self, item: Self::Item, dist: Self) -> bool { + "(and + (= (tuple_proj.1 self_) (tuple_proj.1 dist)) + (= (tuple_proj.0 self_) item) + (= (+ (tuple_proj.0 self_) 1) (tuple_proj.0 dist)) + )"; + true + } +} + +// The call site: a concrete `Range`, folded with a total, unconditioned closure. +// The postcondition is a direct restatement of one conjunct of `fold`'s own ensures, +// so establishing it exercises only the call-site plumbing (requires discharge, +// ensures assumption), not any induction over the produced history. +#[thrust_macros::context] +#[thrust_macros::requires( + Range::invariant((*r).0) + && (*r).0.start == 0 && (*r).0.end == 3 + && (*r).1.len() == 0 + && (*r).2.len() == 1 + && (*r).2[0] == 0 +)] +#[thrust_macros::ensures( + result == (!r).2[(!r).1.len()] && (!r).2.len() == (!r).1.len() + 1 +)] +fn sum_range(r: &mut Run) -> i64 { + let f = thrust_macros::closure!( + requires(true), + ensures(result == a + x), + |a: i64, x: i64| -> i64 { a + x }, + ); + fold(r, 0, f) +} + +// An assumed induction principle over the accumulator history: a chain that never +// decreases ends no lower than it started. The step-wise premise is first order, but +// chaining it across a symbolic-length history is not, which is why this is assumed +// rather than derived. +#[thrust::trusted] +#[thrust_macros::context] +#[thrust_macros::requires( + (*r).2.len() == (*r).1.len() + 1 + && forall(|k: Int| 0 <= k && k < (*r).1.len() ==> (*r).2[k + 1] >= (*r).2[k]) +)] +#[thrust_macros::ensures((*r).2[(*r).1.len()] >= (*r).2[0])] +fn monotone_chain + Model>(r: &Run) {} + +// The call site that needs the induction: the result is at least the initial +// accumulator, which follows from the lemma plus the closure's own postcondition. +#[thrust_macros::context] +#[thrust_macros::requires( + Range::invariant((*r).0) + && (*r).0.start == 0 && (*r).0.end == 3 + && (*r).1.len() == 0 + && (*r).2.len() == 1 + && (*r).2[0] == 0 +)] +#[thrust_macros::ensures(result >= 0)] +fn sum_range_nonneg(r: &mut Run) -> i64 { + let f = thrust_macros::closure!( + requires(true), + ensures(result >= a), + |a: i64, x: i64| -> i64 { if x > 0 { a + x } else { a } }, + ); + let res = fold(&mut *r, 0, f); + monotone_chain(&*r); + res +} + +fn main() { + let range = Range { start: 0, end: 3 }; + let init: i64 = 0; + let mut r = Run { + iter: range, + items: thrust_macros::ghost!(|| -> Seq { Seq::empty() }), + accs: thrust_macros::ghost!(|init: i64| -> Seq { Seq::singleton(init) }), + }; + sum_range(&mut r); + + let mut r2 = Run { + iter: Range { start: 0, end: 3 }, + items: thrust_macros::ghost!(|| -> Seq { Seq::empty() }), + accs: thrust_macros::ghost!(|init: i64| -> Seq { Seq::singleton(init) }), + }; + sum_range_nonneg(&mut r2); +} diff --git a/tests/ui/pass/traits/fold_fn_ghost_noiter.rs b/tests/ui/pass/traits/fold_fn_ghost_noiter.rs new file mode 100644 index 00000000..00b729f9 --- /dev/null +++ b/tests/ui/pass/traits/fold_fn_ghost_noiter.rs @@ -0,0 +1,62 @@ +//@check-pass +//@compile-flags: -C debug-assertions=off -A unused-variables +//@rustc-env: THRUST_SOLVER=tests/thrust-pcsat-wrapper THRUST_SOLVER_TIMEOUT_SECS=60 COAR_IMAGE=coar:latest + +use thrust_models::forall; +use thrust_models::model::{Int, Seq}; +use thrust_models::Ghost; + +struct Run { + items: Ghost>, + accs: Ghost>, +} + +impl thrust_models::Model for Run { + type Ty = (Seq, Seq); +} + +// `Fn` rather than `FnMut`: the closure has no state to project, so the accumulator +// chain needs no prophecy, only the closure's own pre/postcondition. +#[thrust_macros::context] +#[thrust_macros::requires( + n >= 0 + && (*r).0.len() == 0 + && (*r).1.len() == 1 + && (*r).1[0] == init + && forall(|a: Int| forall(|x: Int| thrust_macros::pre!(f(a, x)))) +)] +#[thrust_macros::ensures( + result == (!r).1[(!r).0.len()] + && (!r).1.len() == (!r).0.len() + 1 + && forall(|k: Int| + 0 <= k && k < (!r).0.len() + ==> thrust_macros::post!(f((!r).1[k], (!r).0[k]), (!r).1[k + 1]) + ) +)] +fn fold_upto i64>(r: &mut Run, n: i64, init: i64, f: F) -> i64 { + let rr = r; + let mut acc = init; + let mut i = 0; + while i < n { + thrust_macros::invariant!( + |rr: &mut Run, r: thrust_models::FnParam<&mut Run>, f: F, acc: i64, i: i64, n: i64| + 0 <= i && i <= n + && !rr == !r.at_entry() + && (*rr).0.len() == i + && (*rr).1.len() == (*rr).0.len() + 1 + && acc == (*rr).1[(*rr).0.len()] + && forall(|a: Int| forall(|x: Int| thrust_macros::pre!(f(a, x)))) + && forall(|k: Int| + 0 <= k && k < (*rr).0.len() + ==> thrust_macros::post!(f((*rr).1[k], (*rr).0[k]), (*rr).1[k + 1]) + ) + ); + acc = f(acc, i); + rr.items = thrust_macros::ghost!(|rr: &mut Run, i: i64| -> Seq { (*rr).0.push(i) }); + rr.accs = thrust_macros::ghost!(|rr: &mut Run, acc: i64| -> Seq { (*rr).1.push(acc) }); + i += 1; + } + acc +} + +fn main() {} diff --git a/tests/ui/pass/traits/ghost_count.rs b/tests/ui/pass/traits/ghost_count.rs new file mode 100644 index 00000000..9a976231 --- /dev/null +++ b/tests/ui/pass/traits/ghost_count.rs @@ -0,0 +1,67 @@ +//@check-pass +//@compile-flags: -C debug-assertions=off -A unused-variables +//@rustc-env: THRUST_SOLVER=tests/thrust-pcsat-wrapper THRUST_SOLVER_TIMEOUT_SECS=60 COAR_IMAGE=coar:latest + +use thrust_models::forall; +use thrust_models::model::Seq; +use thrust_models::{Ghost, Model}; + +#[thrust_macros::context] +trait Iterator { + type Item; + + #[thrust_macros::requires(Self::invariant(*self))] + #[thrust_macros::ensures(Self::invariant(!self))] + #[thrust_macros::ensures(result == None ==> Self::completed(self))] + #[thrust_macros::ensures(forall(|i| result == Some(i) ==> Self::step(*self, i, !self)))] + fn next(&mut self) -> Option; + + #[thrust_macros::predicate] + fn invariant(self) -> bool; + #[thrust_macros::predicate] + fn completed(&mut self) -> bool; + #[thrust_macros::predicate] + fn step(self, item: Self::Item, dist: Self) -> bool; +} + +struct Run +where + I::Item: Model, +{ + iter: I, + items: Ghost::Ty>>, +} + +impl Model for Run +where + I::Item: Model, +{ + type Ty = (::Ty, Seq<::Ty>); +} + +// A fold whose accumulator is related to the produced history by the returned value. +#[thrust_macros::context] +#[thrust_macros::requires(I::invariant((*r).0) && (*r).1.len() == 0)] +#[thrust_macros::ensures(result == (!r).1.len())] +fn count(r: &mut Run) -> i64 +where + I::Item: Model, + ::Ty: PartialEq, + ::Ty: PartialEq, +{ + let rr = r; + let mut acc = 0; + while let Some(x) = rr.iter.next() { + thrust_macros::invariant!( + |rr: &mut Run, r: thrust_models::FnParam<&mut Run>, acc: i64| + I::invariant((*rr).0) && !rr == !r.at_entry() && acc == (*rr).1.len() + ); + rr.items = thrust_macros::ghost!( + |rr: &mut Run, x: I::Item| -> Seq<::Ty> { (*rr).1.push(x) } + ); + acc += 1; + } + acc +} + +fn main() {} diff --git a/tests/ui/pass/traits/ghost_produced.rs b/tests/ui/pass/traits/ghost_produced.rs new file mode 100644 index 00000000..57fb2010 --- /dev/null +++ b/tests/ui/pass/traits/ghost_produced.rs @@ -0,0 +1,72 @@ +//@check-pass +//@compile-flags: -C debug-assertions=off -A unused-variables +//@rustc-env: THRUST_SOLVER=tests/thrust-pcsat-wrapper THRUST_SOLVER_TIMEOUT_SECS=60 COAR_IMAGE=coar:latest + +use thrust_models::forall; +use thrust_models::model::{Int, Seq}; +use thrust_models::{Ghost, Model}; + +#[thrust_macros::context] +trait Iterator { + type Item; + + #[thrust_macros::requires(Self::invariant(*self))] + #[thrust_macros::ensures(Self::invariant(!self))] + #[thrust_macros::ensures(result == None ==> Self::completed(self))] + #[thrust_macros::ensures(forall(|i| result == Some(i) ==> Self::step(*self, i, !self)))] + #[thrust_macros::ensures(forall(|i| result == Some(i) ==> Self::item_ok(i)))] + fn next(&mut self) -> Option; + + #[thrust_macros::predicate] + fn invariant(self) -> bool; + #[thrust_macros::predicate] + fn completed(&mut self) -> bool; + #[thrust_macros::predicate] + fn step(self, item: Self::Item, dist: Self) -> bool; + #[thrust_macros::predicate] + fn item_ok(item: Self::Item) -> bool; +} + +// The history of produced items lives in a ghost field, updated by the loop body, +// so the specification never has to existentially quantify it. +struct Run +where + I::Item: Model, +{ + iter: I, + produced: Ghost::Ty>>, +} + +impl Model for Run +where + I::Item: Model, +{ + type Ty = (::Ty, Seq<::Ty>); +} + +#[thrust_macros::context] +#[thrust_macros::requires(I::invariant((*r).0) && (*r).1.len() == 0)] +#[thrust_macros::ensures( + forall(|k: Int| 0 <= k && k < (!r).1.len() ==> I::item_ok((!r).1[k])) +)] +fn drain(r: &mut Run) +where + I::Item: Model, + ::Ty: PartialEq, + ::Ty: PartialEq, +{ + let rr = r; + while let Some(x) = rr.iter.next() { + thrust_macros::invariant!( + |rr: &mut Run, r: thrust_models::FnParam<&mut Run>| + I::invariant((*rr).0) + && !rr == !r.at_entry() + && forall(|k: Int| 0 <= k && k < (*rr).1.len() ==> I::item_ok((*rr).1[k])) + ); + rr.produced = thrust_macros::ghost!( + |rr: &mut Run, x: I::Item| -> Seq<::Ty> { (*rr).1.push(x) } + ); + } +} + +fn main() {} diff --git a/tests/ui/pass/traits/ghost_step_chain.rs b/tests/ui/pass/traits/ghost_step_chain.rs new file mode 100644 index 00000000..e7601380 --- /dev/null +++ b/tests/ui/pass/traits/ghost_step_chain.rs @@ -0,0 +1,80 @@ +//@check-pass +//@compile-flags: -C debug-assertions=off -A unused-variables +//@rustc-env: THRUST_SOLVER=tests/thrust-pcsat-wrapper THRUST_SOLVER_TIMEOUT_SECS=60 COAR_IMAGE=coar:latest + +use thrust_models::forall; +use thrust_models::model::{Int, Seq}; +use thrust_models::{Ghost, Model}; + +#[thrust_macros::context] +trait Iterator { + type Item; + + #[thrust_macros::requires(Self::invariant(*self))] + #[thrust_macros::ensures(Self::invariant(!self))] + #[thrust_macros::ensures(result == None ==> Self::completed(self))] + #[thrust_macros::ensures(forall(|i| result == Some(i) ==> Self::step(*self, i, !self)))] + fn next(&mut self) -> Option; + + #[thrust_macros::predicate] + fn invariant(self) -> bool; + #[thrust_macros::predicate] + fn completed(&mut self) -> bool; + #[thrust_macros::predicate] + fn step(self, item: Self::Item, dist: Self) -> bool; +} + +// The state and item histories that fold's specification used to quantify +// existentially now live in ghost fields the loop body maintains. +struct Run +where + I::Item: Model, +{ + iter: I, + states: Ghost::Ty>>, + items: Ghost::Ty>>, +} + +impl Model for Run +where + I::Item: Model, +{ + type Ty = (::Ty, Seq<::Ty>, Seq<::Ty>); +} + +#[thrust_macros::context] +#[thrust_macros::requires( + I::invariant((*r).0) && (*r).1.len() == 1 && (*r).1[0] == (*r).0 && (*r).2.len() == 0 +)] +#[thrust_macros::ensures( + forall(|k: Int| 0 <= k && k < (!r).2.len() ==> I::step((!r).1[k], (!r).2[k], (!r).1[k + 1])) +)] +fn drain_chain(r: &mut Run) +where + I::Item: Model, + ::Ty: PartialEq, + ::Ty: PartialEq, +{ + let rr = r; + while let Some(x) = rr.iter.next() { + thrust_macros::invariant!( + |rr: &mut Run, r: thrust_models::FnParam<&mut Run>| + I::invariant((*rr).0) + && !rr == !r.at_entry() + && (*rr).1.len() == (*rr).2.len() + 1 + && (*rr).1[(*rr).2.len()] == (*rr).0 + && forall(|k: Int| + 0 <= k && k < (*rr).2.len() + ==> I::step((*rr).1[k], (*rr).2[k], (*rr).1[k + 1]) + ) + ); + rr.items = thrust_macros::ghost!( + |rr: &mut Run, x: I::Item| -> Seq<::Ty> { (*rr).2.push(x) } + ); + rr.states = thrust_macros::ghost!( + |rr: &mut Run| -> Seq<::Ty> { (*rr).1.push((*rr).0) } + ); + } +} + +fn main() {} diff --git a/tests/ui/pass/traits/map_ext_total_pre.rs b/tests/ui/pass/traits/map_ext_total_pre.rs new file mode 100644 index 00000000..1b022248 --- /dev/null +++ b/tests/ui/pass/traits/map_ext_total_pre.rs @@ -0,0 +1,212 @@ +//@check-pass +//@compile-flags: -C debug-assertions=off -A unused-variables +//@rustc-env: THRUST_SOLVER=tests/thrust-pcsat-wrapper THRUST_SOLVER_TIMEOUT_SECS=60 COAR_IMAGE=coar:latest +use thrust_models::forall; +use thrust_models::model::{Int, Seq}; +use thrust_models::{Ghost, Model}; + +#[thrust_macros::context] +trait Iterator { + type Item; + + #[thrust_macros::requires(Self::invariant(*self))] + #[thrust_macros::ensures(Self::invariant(!self))] + #[thrust_macros::ensures(result == None ==> Self::completed(self))] + #[thrust_macros::ensures(forall(|i| result == Some(i) ==> Self::step(*self, i, !self)))] + fn next(&mut self) -> Option; + + #[thrust_macros::predicate] + fn invariant(self) -> bool; + #[thrust_macros::predicate] + fn completed(&mut self) -> bool; + #[thrust_macros::predicate] + fn step(self, item: Self::Item, dist: Self) -> bool; +} + +// A variation on Creusot's `MapExt`, not `MapExt` itself -- the difference is +// in the second paragraph. The closure receives the ghost history of items +// produced so far (`Ghost>`), so its precondition can depend on what +// has come before instead of being stated unconditionally over the whole item +// type (contrast `map_fn_uncond_pre.rs`). The history lives in a ghost `produced` +// field updated by `next` itself -- no existential witness array in `step`. +// +// The precondition here is conditioned on the history alone, not on what the +// inner iterator can still actually produce (Creusot's `next_precondition`, +// which quantifies over `self.iter.produces(...)`): that reachability- +// conditioned form was tried first and is NOT inductive on its own -- Creusot +// pairs it with a `preservation` law justified by `produces_trans`, untried +// here. What DOES verify is `preservation` stated as a fact about the closure +// alone, quantified over an arbitrary history: for ANY history, if the closure +// accepted one more item, its precondition still holds for ANY next item at +// the extended history. That is enough to make the per-position "precondition +// holds for the current history" conjunct inductive, without needing +// `produces`/`produces_refl`/`produces_trans` at all -- at the cost of +// requiring the precondition to hold for every possible next item, not just +// producible ones. +struct Map { + iter: I, + func: F, + produced: Ghost>, +} + +impl Model for Map { + type Ty = Map; +} + +// Obstacle: a `Ghost`-typed FIELD has no model-level accessor in a `ghost!` +// body. `Map`'s model is the struct itself, so `s.produced` stays +// `Ghost>` there and `push` is not found (E0599); reaching it through +// a `&mut Map` the way `fold_fn_ghost_call_law.rs` reaches `Run`'s tuple model +// is not available. A `Ghost` PARAMETER is modelled as `T`, so the push has +// to happen in a function that takes one. Binding the field to a local first +// does not help either -- the ghost term then reports the item as not live. +// +// This is NOT the `Self`-in-a-generic-trait-impl gap that forall-sort c0cfee5 +// fixed; `ghost_in_generic_impl.rs` shows `ghost!` working directly in such an +// impl now. Only the field access keeps this workaround. +#[thrust_macros::ensures(result == produced.push(x))] +fn push_produced(produced: Ghost>, x: i64) -> Ghost> { + thrust_macros::ghost!(|produced: Ghost>, x: i64| -> Seq { produced.push(x) }) +} + +#[thrust_macros::context] +impl + Model, F: Fn(i64, Ghost>) -> i64> Iterator for Map +where + ::Ty: PartialEq, +{ + type Item = i64; + + fn next(&mut self) -> Option { + match self.iter.next() { + Some(v) => { + let r = (self.func)(v, self.produced); + self.produced = push_produced(self.produced, v); + Some(r) + } + None => None, + } + } + + #[thrust_macros::predicate] + fn invariant(self) -> bool { + // self.iter.invariant() && + // forall(|e: i64| pre!(self.func(e, self.produced))) && + // preservation(self.func): forall(|harr, hlen, e1: i64, e2: i64, b: i64| + // pre!(self.func(e1, (harr,hlen))) && post!(self.func(e1, (harr,hlen)), b) + // ==> pre!(self.func(e2, (harr,hlen).push(e1)))) + // + // Obstacle found and worked around here: quantifying with `forall` over + // a variable of the packed `Seq`-model TUPLE sort + // (`Tuple-Int>`) crashes COAR's SMT-LIB2 parser + // (`Failure " is already bound"`, independent of the chosen bound + // name -- confirmed with several). Workaround: quantify over the + // tuple's own FIELDS (an `Array Int Int` and an `Int` length) and + // reconstruct the tuple inline via the `tuple<...>` constructor. + "(and + (q_invariant_bedbd733d3f248df03a4bbf8ef15c8e (tuple_proj-Int>>.0 self_)) + (forall ((e Int)) + (q_pre_next_bedbd733d3f248d989e85efaa8d1bc7 + (tuple_proj-Int>>.1 self_) + e + (tuple_proj-Int>>.2 self_) + ) + ) + (forall ((harr (Array Int Int)) (hlen Int)) + (forall ((e1 Int)) + (forall ((e2 Int)) + (forall ((b Int)) + (=> + (and + (q_pre_next_bedbd733d3f248d989e85efaa8d1bc7 + (tuple_proj-Int>>.1 self_) + e1 + (tuple-Int> harr hlen) + ) + (q_post_next_bedbd733d3f248d989e85efaa8d1bc7 + (tuple_proj-Int>>.1 self_) + e1 + (tuple-Int> harr hlen) + b + ) + ) + (q_pre_next_bedbd733d3f248d989e85efaa8d1bc7 + (tuple_proj-Int>>.1 self_) + e2 + (tuple-Int> (store harr hlen e1) (+ hlen 1)) + ) + ) + ) + ) + ) + ) + )"; + true + } + + #[thrust_macros::predicate] + fn completed(&mut self) -> bool { + // self.iter.completed() && *self.func == !self.func && *self.produced == !self.produced + "(and + (q_completed_bedbd733d3f248d6f3ca13bf4a6f7f6 + (mut + (tuple_proj-Int>>.0 (mut_current-Int>>> self_)) + (tuple_proj-Int>>.0 (mut_final-Int>>> self_)) + ) + ) + (= + (tuple_proj-Int>>.1 (mut_current-Int>>> self_)) + (tuple_proj-Int>>.1 (mut_final-Int>>> self_)) + ) + (= + (tuple_proj-Int>>.2 (mut_current-Int>>> self_)) + (tuple_proj-Int>>.2 (mut_final-Int>>> self_)) + ) + )"; + true + } + + #[thrust_macros::predicate] + fn step(self, item: Self::Item, dist: Self) -> bool { + // exists(|i: i64| self.iter.step(i, dist.iter)) && + // pre!(self.func(i, self.produced)) && post!(self.func(i, self.produced), item) && + // self.func == dist.func && dist.produced == self.produced.push(i) + "(exists ((i Int)) + (and + (q_step_bedbd733d3f248d84d555206bfaa09e + (tuple_proj-Int>>.0 self_) + i + (tuple_proj-Int>>.0 dist) + ) + (q_pre_next_bedbd733d3f248d989e85efaa8d1bc7 + (tuple_proj-Int>>.1 self_) + i + (tuple_proj-Int>>.2 self_) + ) + (q_post_next_bedbd733d3f248d989e85efaa8d1bc7 + (tuple_proj-Int>>.1 self_) + i + (tuple_proj-Int>>.2 self_) + item + ) + (= + (tuple_proj-Int>>.1 self_) + (tuple_proj-Int>>.1 dist) + ) + (= + (tuple_proj-Int>>.2 dist) + (tuple-Int> + (store + (tuple_proj-Int>.0 (tuple_proj-Int>>.2 self_)) + (tuple_proj-Int>.1 (tuple_proj-Int>>.2 self_)) + i + ) + (+ (tuple_proj-Int>.1 (tuple_proj-Int>>.2 self_)) 1) + ) + ) + ) + )"; + true + } +} + +fn main() {} diff --git a/tests/ui/pass/traits/map_fn_concrete_item.rs b/tests/ui/pass/traits/map_fn_concrete_item.rs new file mode 100644 index 00000000..9f6ce72c --- /dev/null +++ b/tests/ui/pass/traits/map_fn_concrete_item.rs @@ -0,0 +1,119 @@ +//@check-pass +//@compile-flags: -C debug-assertions=off -A unused-variables +//@rustc-env: THRUST_SOLVER=tests/thrust-pcsat-wrapper THRUST_SOLVER_TIMEOUT_SECS=60 COAR_IMAGE=coar:latest +use thrust_models::forall; + +#[thrust_macros::context] +trait Iterator { + type Item; + + #[thrust_macros::requires(Self::invariant(*self))] + #[thrust_macros::ensures(Self::invariant(!self))] + #[thrust_macros::ensures(result == None ==> Self::completed(self))] + #[thrust_macros::ensures(forall(|i| result == Some(i) ==> Self::step(*self, i, !self)))] + fn next(&mut self) -> Option; + + #[thrust_macros::predicate] + fn invariant(self) -> bool; + #[thrust_macros::predicate] + fn completed(&mut self) -> bool; + #[thrust_macros::predicate] + fn step(self, item: Self::Item, dist: Self) -> bool; +} + +struct Map { + // The inner iterator + iter: I, + // The mapper + func: F, +} + +impl thrust_models::Model for Map { + type Ty = Map; +} + +// `Map`'s own struct-level type arguments are `` -- fixing the closure's +// output to a concrete `i64` (rather than an impl-only `B: Model` that never +// appears in `Map`'s own type arguments) removes the type parameter that +// panicked when a call site tried to resolve it. +#[thrust_macros::context] +impl + thrust_models::Model, F: Fn(i64) -> i64> Iterator for Map +where + ::Ty: PartialEq, +{ + type Item = i64; + + fn next(&mut self) -> Option { + match self.iter.next() { + Some(v) => { + Some((self.func)(v)) + } + None => None, + } + } + + #[thrust_macros::predicate] + fn invariant(self) -> bool { + // self.iter.invariant() && + // forall(|i: i64| pre!(self.func(i))) + "(and + (q_invariant_4d8c188fb84596fee9a2d9c5fc98ae25 (tuple_proj.0 self_)) + (forall ((i Int)) + (q_pre_next_4d8c188fb84596fecd3dcc87543efe66 + (tuple_proj.1 self_) + i + ) + ) + )"; + true + } + + #[thrust_macros::predicate] + fn completed(&mut self) -> bool { + // self.iter.completed() && *self.func == !self.func + "(and + (q_completed_4d8c188fb84596fe73c306e4bc6f95ef + (mut + (tuple_proj.0 (mut_current> self_)) + (tuple_proj.0 (mut_final> self_)) + ) + ) + (= + (tuple_proj.1 (mut_current> self_)) + (tuple_proj.1 (mut_final> self_)) + ) + )"; + true + } + + #[thrust_macros::predicate] + fn step(self, item: Self::Item, dist: Self) -> bool { + // exists(|i: i64| self.iter.step(i, dist.iter)) && + // pre!(self.func(i)) && post!(self.func(i), item) && self.func == dist.func + "(exists ((i Int)) + (and + (q_step_4d8c188fb84596fe3fcc020be02ea8ac + (tuple_proj.0 self_) + i + (tuple_proj.0 dist) + ) + (q_pre_next_4d8c188fb84596fecd3dcc87543efe66 + (tuple_proj.1 self_) + i + ) + (q_post_next_4d8c188fb84596fecd3dcc87543efe66 + (tuple_proj.1 self_) + i + item + ) + (= + (tuple_proj.1 self_) + (tuple_proj.1 dist) + ) + ) + )"; + true + } +} + +fn main() {} diff --git a/tests/ui/pass/traits/map_fn_uncond_pre.rs b/tests/ui/pass/traits/map_fn_uncond_pre.rs new file mode 100644 index 00000000..5887ae68 --- /dev/null +++ b/tests/ui/pass/traits/map_fn_uncond_pre.rs @@ -0,0 +1,119 @@ +//@check-pass +//@compile-flags: -C debug-assertions=off -A unused-variables +//@rustc-env: THRUST_SOLVER=tests/thrust-pcsat-wrapper THRUST_SOLVER_TIMEOUT_SECS=60 COAR_IMAGE=coar:latest +use thrust_models::forall; + +#[thrust_macros::context] +trait Iterator { + type Item; + + #[thrust_macros::requires(Self::invariant(*self))] + #[thrust_macros::ensures(Self::invariant(!self))] + #[thrust_macros::ensures(result == None ==> Self::completed(self))] + #[thrust_macros::ensures(forall(|i| result == Some(i) ==> Self::step(*self, i, !self)))] + fn next(&mut self) -> Option; + + #[thrust_macros::predicate] + fn invariant(self) -> bool; + #[thrust_macros::predicate] + fn completed(&mut self) -> bool; + #[thrust_macros::predicate] + fn step(self, item: Self::Item, dist: Self) -> bool; +} + +struct Map { + // The inner iterator + iter: I, + // The mapper + func: F, +} + +impl thrust_models::Model for Map { + type Ty = Map; +} + +#[thrust_macros::context] +impl B> Iterator for Map +where ::Ty: PartialEq +{ + type Item = B; + + fn next(&mut self) -> Option { + match self.iter.next() { + Some(v) => { + Some((self.func)(v)) + } + None => None, + } + } + + #[thrust_macros::predicate] + fn invariant(self) -> bool { + // self.iter.invariant() && + // forall(|i: I::Item| pre!(self.func(i))) + // With `Fn`, the closure's precondition is a fixed fact about the item alone + // (no mutable state to track), so it is stated unconditionally instead of + // "for every item the inner iterator could still step to" -- that + // reachability-conditioned form is what made the `FnMut` invariant + // non-inductive. + "(and + (q_invariant_597ac4b22488a2bc34d254b9ac53a96e (tuple_proj.0 self_)) + (forall ((i a3)) + (q_pre_next_597ac4b22488a2bc3015b2bc3056d418 + (tuple_proj.1 self_) + i + ) + ) + )"; + true + } + + #[thrust_macros::predicate] + fn completed(&mut self) -> bool { + // self.iter.completed() && *self.func == !self.func + "(and + (q_completed_597ac4b22488a2bcd79190db0c73456e + (mut + (tuple_proj.0 (mut_current> self_)) + (tuple_proj.0 (mut_final> self_)) + ) + ) + (= + (tuple_proj.1 (mut_current> self_)) + (tuple_proj.1 (mut_final> self_)) + ) + )"; + true + } + + #[thrust_macros::predicate] + fn step(self, item: Self::Item, dist: Self) -> bool { + // exists(|i: Self::Item| self.iter.step(i, dist.iter)) && + // pre!(self.func(i)) && post!(self.func(i), item) && self.func == dist.func + "(exists ((i a3)) + (and + (q_step_597ac4b22488a2bc6c728c715e62f635 + (tuple_proj.0 self_) + i + (tuple_proj.0 dist) + ) + (q_pre_next_597ac4b22488a2bc3015b2bc3056d418 + (tuple_proj.1 self_) + i + ) + (q_post_next_597ac4b22488a2bc3015b2bc3056d418 + (tuple_proj.1 self_) + i + item + ) + (= + (tuple_proj.1 self_) + (tuple_proj.1 dist) + ) + ) + )"; + true + } +} + +fn main() {} From c702e41d6fd568411cb786c97aa7ba82494cf5c0 Mon Sep 17 00:00:00 2001 From: coeff-aij <175928954+coeff-aij@users.noreply.github.com> Date: Wed, 9 Sep 2026 22:28:11 +0900 Subject: [PATCH 141/142] Break the item_ok propagation in ghost_produced's fail twin The twin used to drop the empty-history precondition, which is the base case of the loop invariant: it goes Unsat even when nothing carries `item_ok` across an iteration, so it never pinned the property the pass file is named for. Removing `next`'s `item_ok` postcondition instead takes away the fact's only source. Weakening the loop invariant or shifting the postcondition's index both leave the solver at Unknown -- refuting those needs it to reason about every interpretation of an abstract predicate. Co-Authored-By: Claude Opus 5 (1M context) --- tests/ui/fail/traits/ghost_produced.rs | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/tests/ui/fail/traits/ghost_produced.rs b/tests/ui/fail/traits/ghost_produced.rs index 695a4e42..3f22ca49 100644 --- a/tests/ui/fail/traits/ghost_produced.rs +++ b/tests/ui/fail/traits/ghost_produced.rs @@ -14,7 +14,6 @@ trait Iterator { #[thrust_macros::ensures(Self::invariant(!self))] #[thrust_macros::ensures(result == None ==> Self::completed(self))] #[thrust_macros::ensures(forall(|i| result == Some(i) ==> Self::step(*self, i, !self)))] - #[thrust_macros::ensures(forall(|i| result == Some(i) ==> Self::item_ok(i)))] fn next(&mut self) -> Option; #[thrust_macros::predicate] @@ -45,7 +44,7 @@ where } #[thrust_macros::context] -#[thrust_macros::requires(I::invariant((*r).0))] +#[thrust_macros::requires(I::invariant((*r).0) && (*r).1.len() == 0)] #[thrust_macros::ensures( forall(|k: Int| 0 <= k && k < (!r).1.len() ==> I::item_ok((!r).1[k])) )] From 11283851e2237258cf551a40853dcd2d0c054ce3 Mon Sep 17 00:00:00 2001 From: coeff-aij <175928954+coeff-aij@users.noreply.github.com> Date: Thu, 10 Sep 2026 16:19:09 +0900 Subject: [PATCH 142/142] Check a trait postcondition at the impl, not in the default body `trait_assoc_type_spec` asked Thrust to prove `nonempty`'s postcondition from an empty default body. A default method is checked once against the abstract predicate, so the claim has to hold for every implementor, and nothing in the trait says `produces` is non-empty -- the `pass` file was claiming a capability Thrust does not have (re-checking default bodies per impl) rather than pinning one it does. Declaring `nonempty` without a body moves the obligation to the impl, where `produces` is concrete, and the fail twin's `false` predicate still refutes it. `trait_default_method_spec` keeps the rejected shape as its own pair, so the distinction between the two is pinned rather than lost. Co-Authored-By: Claude Opus 5 --- tests/ui/fail/trait_assoc_type_spec.rs | 4 +- tests/ui/fail/trait_default_method_spec.rs | 44 +++++++++++++++++++++ tests/ui/pass/trait_assoc_type_spec.rs | 4 +- tests/ui/pass/trait_default_method_spec.rs | 46 ++++++++++++++++++++++ 4 files changed, 96 insertions(+), 2 deletions(-) create mode 100644 tests/ui/fail/trait_default_method_spec.rs create mode 100644 tests/ui/pass/trait_default_method_spec.rs diff --git a/tests/ui/fail/trait_assoc_type_spec.rs b/tests/ui/fail/trait_assoc_type_spec.rs index cb58fd5d..5cbc0d4d 100644 --- a/tests/ui/fail/trait_assoc_type_spec.rs +++ b/tests/ui/fail/trait_assoc_type_spec.rs @@ -10,7 +10,7 @@ trait Source { fn produces(self, x: Self::Item) -> bool; #[thrust_macros::ensures(thrust_models::exists(|x| Self::produces(*self, x)))] - fn nonempty(&self) {} + fn nonempty(&self); } #[derive(PartialEq)] @@ -31,6 +31,8 @@ impl Source for S { "false"; false } + + fn nonempty(&self) {} } fn main() { diff --git a/tests/ui/fail/trait_default_method_spec.rs b/tests/ui/fail/trait_default_method_spec.rs new file mode 100644 index 00000000..3d00b3fc --- /dev/null +++ b/tests/ui/fail/trait_default_method_spec.rs @@ -0,0 +1,44 @@ +//@error-in-other-file: Unsat +//@compile-flags: -Adead_code -C debug-assertions=off +//@rustc-env: THRUST_SOLVER=tests/thrust-pcsat-wrapper COAR_IMAGE=coar:latest + +// A default method's postcondition is checked once against the abstract predicate, so it +// has to hold for every implementor: `produces` is non-empty in the only impl here, but +// nothing in the trait says so, and the empty default body cannot establish it. + +#[thrust_macros::context] +trait Source { + type Item; + + #[thrust_macros::predicate] + fn produces(self, x: Self::Item) -> bool; + + #[thrust_macros::ensures(thrust_models::exists(|x| Self::produces(*self, x)))] + fn nonempty(&self) {} +} + +#[derive(PartialEq)] +struct S { + v: i64, +} + +impl thrust_models::Model for S { + type Ty = S; +} + +#[thrust_macros::context] +impl Source for S { + type Item = i64; + + #[thrust_macros::predicate] + fn produces(self, x: Self::Item) -> bool { + // x == self.v + "(= x (tuple_proj.0 self_))"; + true + } +} + +fn main() { + let s = S { v: 1 }; + s.nonempty(); +} diff --git a/tests/ui/pass/trait_assoc_type_spec.rs b/tests/ui/pass/trait_assoc_type_spec.rs index bb259597..3921e1e3 100644 --- a/tests/ui/pass/trait_assoc_type_spec.rs +++ b/tests/ui/pass/trait_assoc_type_spec.rs @@ -10,7 +10,7 @@ trait Source { fn produces(self, x: Self::Item) -> bool; #[thrust_macros::ensures(thrust_models::exists(|x| Self::produces(*self, x)))] - fn nonempty(&self) {} + fn nonempty(&self); } #[derive(PartialEq)] @@ -32,6 +32,8 @@ impl Source for S { "(= x (tuple_proj.0 self_))"; true } + + fn nonempty(&self) {} } fn main() { diff --git a/tests/ui/pass/trait_default_method_spec.rs b/tests/ui/pass/trait_default_method_spec.rs new file mode 100644 index 00000000..47812c21 --- /dev/null +++ b/tests/ui/pass/trait_default_method_spec.rs @@ -0,0 +1,46 @@ +//@check-pass +//@compile-flags: -Adead_code -C debug-assertions=off +//@rustc-env: THRUST_SOLVER=tests/thrust-pcsat-wrapper COAR_IMAGE=coar:latest + +// A required method carries its postcondition into each impl, where `produces` is concrete +// and the witness exists. The `fail` twin keeps `nonempty` a default method, whose +// postcondition is checked against the abstract predicate for every implementor instead. + +#[thrust_macros::context] +trait Source { + type Item; + + #[thrust_macros::predicate] + fn produces(self, x: Self::Item) -> bool; + + #[thrust_macros::ensures(thrust_models::exists(|x| Self::produces(*self, x)))] + fn nonempty(&self); +} + +#[derive(PartialEq)] +struct S { + v: i64, +} + +impl thrust_models::Model for S { + type Ty = S; +} + +#[thrust_macros::context] +impl Source for S { + type Item = i64; + + #[thrust_macros::predicate] + fn produces(self, x: Self::Item) -> bool { + // x == self.v + "(= x (tuple_proj.0 self_))"; + true + } + + fn nonempty(&self) {} +} + +fn main() { + let s = S { v: 1 }; + s.nonempty(); +}