diff --git a/src/analyze.rs b/src/analyze.rs index fff5ad1a..0089b57a 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; @@ -159,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: Option, +} + #[derive(Debug, Clone)] enum DefTy<'tcx> { Concrete(rty::RefinedType), + Generic(GenericDefTy<'tcx>), Deferred(DeferredDefTy<'tcx>), } @@ -197,6 +207,13 @@ impl refine::EnumDefProvider for Rc> { } pub type Env = refine::Env>>; +pub type TypeParamMap<'tcx> = HashMap, ForallSortIdx>; + +#[derive(Eq, PartialEq, Hash, Debug, Clone)] +pub enum TypeParam<'tcx> { + GenericType(DefId, u32), + AssocType(DefId, mir_ty::GenericArgsRef<'tcx>), +} #[derive(Debug, Clone)] struct DeferredFormulaFnDef<'tcx> { @@ -224,6 +241,9 @@ pub struct Analyzer<'tcx> { def_ids: did_cache::DefIdCache<'tcx>, enum_defs: Rc>, + + type_params: Rc>>, + closure_type_params: Rc, rty::FunctionType>>>, } impl<'tcx> crate::refine::TemplateRegistry for Analyzer<'tcx> { @@ -251,6 +271,8 @@ impl<'tcx> Analyzer<'tcx> { let system = Default::default(); let basic_blocks = Default::default(); let enum_defs = Default::default(); + let type_params = Default::default(); + let closure_type_params = Default::default(); Self { tcx, defs, @@ -259,6 +281,8 @@ impl<'tcx> Analyzer<'tcx> { basic_blocks, def_ids: did_cache::DefIdCache::new(tcx), enum_defs, + type_params, + closure_type_params, } } @@ -292,7 +316,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 { @@ -387,19 +411,40 @@ impl<'tcx> Analyzer<'tcx> { ?mode, "register_deferred_def" ); - self.defs.insert( - target_def_id, + 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 register_generic_def( + &mut self, + target_def_id: DefId, + local_def_id: LocalDefId, + rty: Option, + ) { + tracing::info!(?target_def_id, ?local_def_id, ?rty, "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 get_closure_type(&self, type_param: TypeParam<'tcx>) -> 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), + DefTy::Generic(GenericDefTy { rty, .. }) => rty.as_ref(), DefTy::Deferred(_) => None, }) } @@ -412,9 +457,17 @@ 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.closure_type_params.clone(), + self.system.clone(), + ); 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( @@ -431,6 +484,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)?; @@ -441,7 +495,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() @@ -451,53 +505,62 @@ 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, generic_args: mir_ty::GenericArgsRef<'tcx>, + caller_def_id: DefId, ) -> Option { - let type_builder = TypeBuilder::new(self.tcx, self.def_ids(), 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 type_builder = self.type_builder(self.def_ids(), caller_def_id); + + 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(); + 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::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 // TODO: remove this after annotation V2 - expected.instantiate_ty_params( - generic_args - .types() - .map(|ty| type_builder.build(ty)) - .map(rty::RefinedType::unrefined) - .collect(), - ); - deferred_ty_cache + Self::instantiate_generic_args(&mut expected, generic_args, &type_builder); + 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 { @@ -637,8 +700,20 @@ 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>, owner_fn_id: DefId) -> TypeBuilder<'tcx> { + TypeBuilder::new( + self.tcx, + def_ids, + owner_fn_id, + self.type_params.clone(), + self.closure_type_params.clone(), + self.system.clone(), + ) } pub fn solve(&mut self) { @@ -732,6 +807,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, @@ -762,7 +838,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 @@ -781,6 +859,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>, @@ -812,7 +891,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 @@ -883,6 +964,7 @@ impl<'tcx> Analyzer<'tcx> { &self, local_def_id: LocalDefId, generic_args: mir_ty::GenericArgsRef<'tcx>, + owner_fn_id: DefId, ) -> Vec<(rty::TypePosition, rty::Refinement)> { let mut out = Vec::new(); for (position, def_id) in self.extract_refinement_paths(local_def_id) { @@ -892,7 +974,9 @@ impl<'tcx> Analyzer<'tcx> { def_id ); }; - 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!( "refinement_path annotation {:?} is not a formula function", formula_def_id diff --git a/src/analyze/annot_fn.rs b/src/analyze/annot_fn.rs index 10e65112..5375da32 100644 --- a/src/analyze/annot_fn.rs +++ b/src/analyze/annot_fn.rs @@ -1,13 +1,16 @@ 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}; +use rustc_middle::ty::{self as mir_ty, TyCtxt, TypeFoldable}; 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; @@ -177,7 +180,14 @@ 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.closure_type_params.clone(), + analyzer.system.clone(), + ); let mut translator = Self { tcx, local_def_id, @@ -198,12 +208,15 @@ 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.closure_type_params.clone(), + self.analyzer.system.clone(), ); self } @@ -237,29 +250,52 @@ 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) + 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> { 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) + self.tcx + .try_normalize_erasing_regions(typing_env, instantiated) + .unwrap_or(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, @@ -292,12 +328,127 @@ 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.type_builder.owner_fn_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; + } + 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 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:#?}"); + 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 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) + .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 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)) + } + /// 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. @@ -659,8 +810,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 (mut 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, @@ -671,11 +826,28 @@ impl<'a, 'tcx> AnnotFnTranslator<'a, 'tcx> { let pred_def_id = if let Some(instance) = instance { instance.def_id() } else { + is_unresolved_args = true; def_id }; - let pred = refine::user_defined_pred(self.tcx, pred_def_id); + + let pred = if is_unresolved_args { + 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, type_params); + self.analyzer + .system + .borrow_mut() + .register_forall_pred(pred.clone()); + pred.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.into(), arg_terms); + let atom = chc::Atom::new(pred, arg_terms); return FormulaOrTerm::Formula(chc::Formula::Atom(atom)); } } diff --git a/src/analyze/basic_block.rs b/src/analyze/basic_block.rs index 835a6623..6fd6e0b5 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<'tcx>), +} + 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,49 +835,67 @@ 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>, ) -> 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; } - 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: {:?}", @@ -880,7 +903,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) 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 @@ -895,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 }; @@ -1333,6 +1359,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(); @@ -1340,7 +1367,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(), owner_fn_id); Self { ctx, tcx, diff --git a/src/analyze/basic_block/visitor/rust_call.rs b/src/analyze/basic_block/visitor/rust_call.rs index b46c0467..16feb1bb 100644 --- a/src/analyze/basic_block/visitor/rust_call.rs +++ b/src/analyze/basic_block/visitor/rust_call.rs @@ -70,7 +70,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) { diff --git a/src/analyze/crate_.rs b/src/analyze/crate_.rs index 74198f18..2188b099 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. /// @@ -70,7 +70,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() { @@ -113,26 +112,18 @@ 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; 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); - } + 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(target_def_id, expected); + self.ctx.register_def(owner_fn_id, expected); } } @@ -142,25 +133,19 @@ 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; }; // 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(), - ); - expected.subst_ty_params(&subst); + let expected = expected.clone(); let generic_args = self.placeholder_generic_args(*local_def_id); self.ctx .local_def_analyzer(*local_def_id) @@ -198,13 +183,13 @@ 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) { - panic!( - "unable to check generic function with constrained type parameter: {}", - self.tcx.def_path_str(local_def_id) - ); - } - self.tcx.types.i32.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 } mir_ty::GenericParamDefKind::Const { .. } => { unimplemented!() 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/analyze/local_def.rs b/src/analyze/local_def.rs index f1f5bdd2..c26d6da2 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) + self.ctx + .def_ty_with_args(trait_item_did, trait_ref.args, trait_ref.def_id) } // TODO: Remove this eager precompute together with @@ -325,11 +397,10 @@ 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); let inst = self .tcx - .normalize_erasing_regions(mir_ty::TypingEnv::fully_monomorphized(), inst); + .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())) @@ -340,7 +411,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 +459,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 +467,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 +477,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()); @@ -435,9 +512,11 @@ impl<'tcx, 'ctx> Analyzer<'tcx, 'ctx> { assert!(require_annot.is_none() || param_annots.is_empty()); assert!(ensure_annot.is_none() || ret_annot.is_none()); - let refinement_annots = self - .ctx - .extract_refinement_annots(self.local_def_id, self.generic_args); + let refinement_annots = self.ctx.extract_refinement_annots( + self.local_def_id, + self.generic_args, + self.owner_fn_id, + ); let trait_item_ty = self.trait_item_ty(); let is_fully_annotated = self.is_fully_annotated(); @@ -482,62 +561,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 { @@ -976,7 +1000,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()); @@ -1095,7 +1119,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); @@ -1223,12 +1247,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 = TypeBuilder::new(tcx, 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, @@ -1240,6 +1270,12 @@ 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.type_builder = self.ctx.type_builder(self.ctx.def_ids(), 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 = diff --git a/src/chc.rs b/src/chc.rs index 58166391..9861319a 100644 --- a/src/chc.rs +++ b/src/chc.rs @@ -1,5 +1,8 @@ //! A multi-sorted CHC system with tuples. +use std::collections::{HashMap, HashSet}; +use std::hash::Hash; + use pretty::{termcolor, Pretty}; use rustc_index::IndexVec; @@ -84,6 +87,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 +139,7 @@ pub enum Sort { Tuple(Vec), Array(Box, Box), Datatype(DatatypeSort), + Forall(ForallSortIdx), } impl From for Sort { @@ -154,6 +197,7 @@ where } } Sort::Datatype(sort) => sort.pretty(allocator), + Sort::Forall(idx) => idx.pretty(allocator), } } } @@ -180,7 +224,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 +310,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), @@ -994,6 +1047,49 @@ impl UserDefinedPred { } } +#[derive(Debug, Clone, PartialEq, Eq, Hash)] +pub struct ForallPred { + inner: String, + type_parameters: Vec, +} + +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>, + D::Doc: Clone, +{ + fn pretty(self, allocator: &'a D) -> pretty::DocBuilder<'a, D, termcolor::ColorSpec> { + let args = allocator.intersperse( + self.type_parameters.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, args: Vec) -> Self { + Self { + inner, + type_parameters: args, + } + } +} + /// A predicate. #[derive(Debug, Clone, PartialEq, Eq, Hash)] pub enum Pred { @@ -1001,6 +1097,7 @@ pub enum Pred { Var(PredVarId), Matcher(MatcherPred), UserDefined(UserDefinedPred), + ForallPred(ForallPred), } impl std::fmt::Display for Pred { @@ -1010,6 +1107,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), } } } @@ -1025,6 +1123,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), } } } @@ -1053,6 +1152,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 { @@ -1060,6 +1165,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(), } } @@ -1069,6 +1175,7 @@ impl Pred { Pred::Var(_) => false, Pred::Matcher(_) => false, Pred::UserDefined(_) => false, + Pred::ForallPred(_) => false, } } @@ -1078,6 +1185,7 @@ impl Pred { Pred::Var(_) => false, Pred::Matcher(_) => false, Pred::UserDefined(_) => false, + Pred::ForallPred(_) => false, } } @@ -1087,6 +1195,7 @@ impl Pred { Pred::Var(_) => false, Pred::Matcher(_) => false, Pred::UserDefined(_) => false, + Pred::ForallPred(_) => false, } } @@ -1096,6 +1205,20 @@ impl Pred { Pred::Var(_) => false, Pred::Matcher(_) => false, Pred::UserDefined(_) => false, + Pred::ForallPred(_) => false, + } + } +} + +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:#?}." + )) } } } @@ -1775,6 +1898,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 { @@ -1783,6 +1933,9 @@ 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, + forall_pred_vars: HashSet, } impl System { @@ -1790,6 +1943,17 @@ impl System { self.pred_vars.push(PredVarDef { sig, debug_info }) } + pub fn register_forall_pred(&mut self, pred: ForallPred) { + self.forall_pred_vars.insert(pred); + } + + 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) } @@ -1812,6 +1976,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/format_context.rs b/src/chc/format_context.rs index 94548274..ce583c2b 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), } } } @@ -347,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.type_parameters); + 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 e8886ed6..fdc2abe6 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. @@ -232,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() { @@ -589,6 +592,72 @@ impl<'ctx, 'a> UserDefinedPredDef<'ctx, 'a> { Self { ctx, inner } } } + +pub struct ForallPredDef<'ctx, 'a> { + ctx: &'ctx FormatContext, + 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 = self + .pred + .type_parameters + .iter() + .map(|sort| self.ctx.fmt_sort(sort)); + let params = List::closed(params); + write!( + f, + "(declare-forall-fun {name} {params} Bool)", + name = self.ctx.forall_pred(self.pred), + ) + } +} + +impl<'ctx, 'a> ForallPredDef<'ctx, 'a> { + pub fn new(ctx: &'ctx FormatContext, pred: &'a chc::ForallPred) -> Self { + Self { ctx, pred } + } +} + +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.iter().map(|p| self.ctx.forall_pred(p))), + 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> { @@ -600,6 +669,14 @@ 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 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))?; @@ -620,16 +697,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!( diff --git a/src/chc/unbox.rs b/src/chc/unbox.rs index b3b24d52..8817e0a9 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), } } @@ -72,6 +73,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), } } @@ -162,6 +164,14 @@ fn unbox_user_defined_pred_def(user_defined_pred_def: UserDefinedPredDef) -> Use UserDefinedPredDef { symbol, sig, body } } +fn unbox_forall_pred_var_def(pred: ForallPred) -> ForallPred { + 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. /// /// The box values in Thrust represent an owned pointer, but are logically equivalent to the inner type. @@ -174,6 +184,9 @@ pub fn unbox(system: System) -> System { user_defined_pred_defs, clauses, 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(); @@ -182,11 +195,18 @@ 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, user_defined_pred_defs, clauses, pred_vars, + forall_sorts, + num_forall_sort_idx, + forall_pred_vars, } } diff --git a/src/refine.rs b/src/refine.rs index 5a1fd8d3..5ecd82c9 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, Sort, 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, args: Vec) -> ForallPred { + ForallPred::new(stable_def_id_symbol(tcx, did, "q"), args) } diff --git a/src/refine/template.rs b/src/refine/template.rs index fe200bda..7cb7e5d9 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, TypeParam, TypeParamMap}; use crate::chc; use crate::refine; use crate::rty; @@ -71,17 +73,28 @@ where pub struct TypeBuilder<'tcx> { tcx: mir_ty::TyCtxt<'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>>, + closure_type_params: Rc, rty::FunctionType>>>, + 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); + pub fn new( + tcx: mir_ty::TyCtxt<'tcx>, + def_ids: DefIdCache<'tcx>, + owner_fn_id: DefId, + type_params: Rc>>, + closure_type_params: Rc, rty::FunctionType>>>, + 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); @@ -93,21 +106,67 @@ impl<'tcx> TypeBuilder<'tcx> { mir_ty::GenericParamDefKind::Const { .. } => {} } } - 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, + owner_fn_id, typing_env, param_idx_mapping, + type_params, + closure_type_params, + system, } } fn translate_param_type(&self, ty: &mir_ty::ParamTy) -> rty::Type { - let index = *self + let param_local_idx = *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 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(); + tracing::debug!( + "issue the new ForallSortIdx {} for ParamTy {:?} at {:?}.", + idx, + ty, + self.owner_fn_id + ); + idx + }); + rty::ParamType::new(param_local_idx, *forall_sort_idx).into() + } + + 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, 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 + }); + + 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<'tcx>, + 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}>. @@ -152,19 +211,36 @@ 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 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) + } else { + false + } + }); + if !contains_model_ty_alias { + return normalized_ty; + } } + tracing::debug!("the type {:#?} is replaced as the {:#?}.", orig_ty, ty); ty } @@ -212,6 +288,10 @@ 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); + rty::PointerType::mut_to(elem_ty).into() + } mir_ty::TyKind::Tuple(ts) => { // elaboration: all fields are boxed let elems = ts @@ -237,6 +317,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 @@ -258,6 +355,19 @@ impl<'tcx> TypeBuilder<'tcx> { unimplemented!("unsupported ADT: {:?}", 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), } } @@ -398,6 +508,10 @@ 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); + rty::PointerType::mut_to(elem_ty).into() + } mir_ty::TyKind::Tuple(ts) => { // elaboration: all fields are boxed let elems = ts @@ -418,6 +532,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<_, _> = diff --git a/src/rty.rs b/src/rty.rs index 7311c4d7..6ea5686e 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}; @@ -803,7 +803,8 @@ impl EnumType { /// A type parameter. #[derive(Debug, Clone)] pub struct ParamType { - pub idx: TypeParamIdx, + type_param_idx: TypeParamIdx, + forall_sort_idx: ForallSortIdx, } impl<'a, D> Pretty<'a, D, termcolor::ColorSpec> for &ParamType @@ -811,17 +812,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 { @@ -829,6 +837,58 @@ 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> { + 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, 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. #[derive(Debug, Clone)] pub struct ArrayType { @@ -920,6 +980,7 @@ pub enum Type { String, Never, Param(ParamType), + Alias(AliasType), Pointer(PointerType), Function(FunctionType), Tuple(TupleType), @@ -933,6 +994,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) @@ -976,6 +1043,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), @@ -1108,7 +1176,8 @@ 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.forall_sort_index()), + Type::Alias(ty) => chc::Sort::Forall(ty.forall_sort_index()), Type::Pointer(ty) => { let elem_sort = ty.elem.ty.to_sort(); @@ -1146,6 +1215,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)), @@ -1164,6 +1234,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)), @@ -1183,6 +1254,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()), @@ -1194,7 +1266,12 @@ 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::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(), @@ -1805,7 +1882,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, @@ -1814,6 +1891,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(); @@ -1841,15 +1931,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()), ) } @@ -1861,6 +1951,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), } } @@ -1875,7 +1983,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) { diff --git a/src/rty/subtyping.rs b/src/rty/subtyping.rs index 03477f02..71196fde 100644 --- a/src/rty/subtyping.rs +++ b/src/rty/subtyping.rs @@ -123,6 +123,10 @@ where let cs2 = self.relate_sub_refined_type(&got.elem, &expected.elem); clauses.extend(cs2); } + (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(), diff --git a/std.rs b/std.rs index 132e3859..ef846777 100644 --- a/std.rs +++ b/std.rs @@ -427,19 +427,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)] 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() {} diff --git a/tests/ui/pass/traits/fold.rs b/tests/ui/pass/traits/fold.rs new file mode 100644 index 00000000..2265ec43 --- /dev/null +++ b/tests/ui/pass/traits/fold.rs @@ -0,0 +1,145 @@ +//@check-pass +//@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) + || 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::invariant_context] + #[thrust_macros::requires(true)] + #[thrust_macros::ensures( + 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[l - 1]) && + result == acc.0[l - 1] && + !( + 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] + ) + ) + ) + ) + ) + )))) + )] + 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() { + 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 + } +} + +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 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() {} 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/simple_loop_call.rs b/tests/ui/pass/traits/simple_loop_call.rs new file mode 100644 index 00000000..39115fd8 --- /dev/null +++ b/tests/ui/pass/traits/simple_loop_call.rs @@ -0,0 +1,49 @@ +//@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 +} + +#[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 + } + + #[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() {} 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() {} 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() {}