123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448(* This file is part of the Catala compiler, a specification language for tax
and social benefits computation rules. Copyright (C) 2022 Inria, contributor:
Denis Merigoux <denis.merigoux@inria.fr>, Alain Delaët
<alain.delaet--tixeuil@inria.fr>, Aymeric Fromherz
<aymeric.fromherz@inria.fr>
Licensed under the Apache License, Version 2.0 (the "License"); you may not
use this file except in compliance with the License. You may obtain a copy of
the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
License for the specific language governing permissions and limitations under
the License. *)openCatala_utilsopenShared_astopenDcalcopenAst(** {1 Helpers and type definitions}*)typevc_return=typedexpr(** The return type of VC generators is the VC expression *)typectx={current_scope_name:ScopeName.t;decl:decl_ctx;input_vars:typedexprVar.tlist;scope_variables_typs:(typedexpr,typ)Var.Map.t;}letrecconjunction_exprs(exprs:typedexprlist)(mark:typedmark):typedexpr=matchexprswith|[]->ELit(LBooltrue),mark|hd::tl->(EAppOp{op=And,Expr.mark_posmark;tys=[TLitTBool,Expr.poshd;TLitTBool,Expr.poshd];args=[hd;conjunction_exprstlmark];},mark)letconjunction(args:vc_returnlist)(mark:typedmark):vc_return=letacc,list=matchargswithhd::tl->hd,tl|[]->(ELit(LBooltrue),mark),[]inList.fold_left(funaccarg->(EAppOp{op=And,Expr.mark_posmark;tys=[TLitTBool,Expr.posacc;TLitTBool,Expr.posarg];args=[arg;acc];},mark))acclistletnegation(arg:vc_return)(mark:typedmark):vc_return=(EAppOp{op=Not,Expr.mark_posmark;tys=[TLitTBool,Expr.posarg];args=[arg];},mark)letdisjunction(args:vc_returnlist)(mark:typedmark):vc_return=letacc,list=matchargswithhd::tl->hd,tl|[]->(ELit(LBoolfalse),mark),[]inList.fold_left(fun(acc:vc_return)arg->(EAppOp{op=Or,Expr.mark_posmark;tys=[TLitTBool,Expr.posacc;TLitTBool,Expr.posarg];args=[arg;acc];},mark))acclist(** [half_product [a1,...,an] [b1,...,bm] returns [(a1,b1),...(a1,bn),...(an,b1),...(an,bm)]] *)lethalf_product(l1:'alist)(l2:'blist):('a*'b)list=l1|>List.mapi(funiei->List.filteri(funj_->i<j)l2|>List.map(funej->ei,ej))|>List.concat(** This code skims through the topmost layers of the terms like this:
[log (error_on_empty < reentrant_variable () | true :- e1 >)] for scope
variables, or [fun () -> e1] for subscope variables. But what we really want
to analyze is only [e1], so we match this outermost structure explicitely
and have a clean verification condition generator that only runs on [e1] *)letmatch_and_ignore_outer_reentrant_default(ctx:ctx)(e:typedexpr):typedexpr=matchMark.removeewith|EErrorOnEmpty(EDefault{excepts=[(EApp{f=EVarx,_;args=[(ELitLUnit,_)];_},_)];just=ELit(LBooltrue),_;cons;},_)whenList.exists(funx'->Var.equalxx')ctx.input_vars->(* scope variables*)cons|EAbs{binder;tys=[(TLitTUnit,_)]}->(* context sub-scope variables *)let_,body=Bindlib.unmbindbinderinbody|EAbs{binder;_}->((* context scope variables *)let_,body=Bindlib.unmbindbinderinmatchMark.removebodywith|EErrorOnEmptye->e|_->Message.error~pos:(Expr.pose)"Internal error: this expression does not have the structure expected \
by the VC generator:\n\
%a"(Print.expr())e)|EErrorOnEmptyd->d(* input subscope variables and non-input scope variable *)|_->e(** {1 Verification conditions generator}*)(** [generate_vc_must_not_return_empty e] returns the dcalc boolean expression
[b] such that if [b] is true, then [e] will never return an empty error. It
also returns a map of all the types of locally free variables inside the
expression. *)letrecgenerate_vc_must_not_return_empty(ctx:ctx)(e:typedexpr):vc_return=matchMark.removeewith|EAbs{binder;_}->(* Hot take: for a function never to return an empty error when called, it
has to do so whatever its input. So we universally quantify over the
variable of the function when inspecting the body, resulting in simply
traversing through in the code here. *)let_vars,body=Bindlib.unmbindbinderin(generate_vc_must_not_return_emptyctx)body|EDefault{excepts;just;cons}->(* <e1 ... en | ejust :- econs > never returns empty if and only if: - first
we look if e1 .. en ejust can return empty; - if no, we check that if
ejust is true, whether econs can return empty. *)disjunction(List.map(generate_vc_must_not_return_emptyctx)excepts@[conjunction[generate_vc_must_not_return_emptyctxjust;(letvc_just_expr=generate_vc_must_not_return_emptyctxconsin(EIfThenElse{cond=just;(* Comment from Alain: the justification is not checked for
holding an default term. In such cases, we need to
encode the logic of the default terms within the
generation of the verification condition
(Z3encoding.translate_expr). Answer from Denis:
Normally, there is a structural invariant from the
surface language to intermediate representation
translation preventing any default terms to appear in
justifications.*)etrue=vc_just_expr;efalse=ELit(LBoolfalse),Mark.gete;},Mark.gete));](Mark.gete);])(Mark.gete)|EEmpty->Mark.copye(ELit(LBoolfalse))|EVar_(* Per default calculus semantics, you cannot call a function with an argument
that evaluates to the empty error. Thus, all variable evaluate to
non-empty-error terms. *)|ELit_->Mark.copye(ELit(LBooltrue))|EApp{f;args;_}->(* Invariant: For the [EApp] case, we assume here that function calls never
return empty error, which implies all functions have been checked never
to return empty errors. *)conjunction(generate_vc_must_not_return_emptyctxf::List.flatten(List.map(funarg->matchMark.removeargwith|EStruct{fields;_}->List.map(funfield->matchMark.removefieldwith|EAbs{binder;tys=[(TLitTUnit,_)]}->((* Invariant: when calling a function with a thunked
emptyerror, this means we're in a direct scope call
with a context argument. In that case, we don't apply
the standard [EAbs] rule and suppose, in coherence
with the [EApp] invariant, that the subscope will
never return empty error so the thunked emptyerror
can be ignored *)let_vars,body=Bindlib.unmbindbinderinmatchMark.removebodywith|EEmpty->Mark.copyfield(ELit(LBooltrue))|_->(* same as basic [EAbs case]*)generate_vc_must_not_return_emptyctxfield)|_->generate_vc_must_not_return_emptyctxfield)(StructField.Map.valuesfields)|_->[generate_vc_must_not_return_emptyctxarg])args))(Mark.gete)|_->conjunction(Expr.shallow_fold(funeacc->generate_vc_must_not_return_emptyctxe::acc)e[])(Mark.gete)(** [generate_vc_must_not_return_conflict e] returns the dcalc boolean
expression [b] such that if [b] is true, then [e] will never return a
conflict error. It also returns a map of all the types of locally free
variables inside the expression. *)letrecgenerate_vc_must_not_return_conflict(ctx:ctx)(e:typedexpr):vc_return=(* See the code of [generate_vc_must_not_return_empty] for a list of
invariants on which this function relies on. *)matchMark.removeewith|EAbs{binder;_}->let_vars,body=Bindlib.unmbindbinderin(generate_vc_must_not_return_conflictctx)body|EVar_|ELit_->Mark.copye(ELit(LBooltrue))|EDefault{excepts;just;cons}->(* <e1 ... en | ejust :- econs > never returns conflict if and only if: -
neither e1 nor ... nor en nor ejust nor econs return conflict - there is
no two differents ei ej that are not empty. *)letquadratic=negation(disjunction(List.map(fun(e1,e2)->conjunction[generate_vc_must_not_return_emptyctxe1;generate_vc_must_not_return_emptyctxe2;](Mark.gete))(half_productexceptsexcepts))(Mark.gete))(Mark.gete)inletothers=List.map(generate_vc_must_not_return_conflictctx)(just::cons::excepts)inletout=conjunction(quadratic::others)(Mark.gete)inout|_->conjunction(Expr.shallow_fold(funeacc->generate_vc_must_not_return_conflictctxe::acc)e[])(Mark.gete)(** {1 Interface}*)typeverification_condition_kind=NoEmptyError|NoOverlappingExceptionstypeverification_condition={vc_guard:typedexpr;(* should have type bool *)vc_kind:verification_condition_kind;(* All assertions defined at the top-level of the scope corresponding to this
assertion *)vc_asserts:typedexpr;vc_scope:ScopeName.t;vc_variable:typedexprVar.tMark.pos;}lettrivial_asserte=Mark.copye(ELit(LBooltrue))letrecgenerate_verification_conditions_scope_body_expr(ctx:ctx)(scope_body_expr:'mexprscope_body_expr):ctx*verification_conditionlist*typedexprlist=matchscope_body_exprwith|Last_->ctx,[],[]|Cons(scope_let,scope_let_next)->letscope_let_var,scope_let_next=Bindlib.unbindscope_let_nextinletnew_ctx,vc_list,assert_list=matchscope_let.scope_let_kindwith|Assertion->(lete=Expr.unbox(Expr.remove_logging_callsscope_let.scope_let_expr)inmatchMark.removeewith|EAsserte->lete=match_and_ignore_outer_reentrant_defaultctxeinctx,[],[e]|_->Message.error~pos:(Expr.pose)"Internal error: this assertion does not have the structure \
expected by the VC generator:\n\
%a"(Print.expr())e)|DestructuringInputStruct->{ctxwithinput_vars=scope_let_var::ctx.input_vars},[],[]|ScopeVarDefinition|SubScopeVarDefinition->(* For scope variables, we should check both that they never evaluate to
emptyError nor conflictError. But for subscope variable definitions,
what we're really doing is adding exceptions to something defined in
the subscope so we just ought to verify only that the exceptions
overlap. *)lete=Expr.unbox(Expr.remove_logging_callsscope_let.scope_let_expr)inlete=match_and_ignore_outer_reentrant_defaultctxeinletvc_confl=generate_vc_must_not_return_conflictctxeinletvc_confl=ifGlobals.optimize()thenExpr.unbox(Shared_ast.Optimizations.optimize_exprctx.declvc_confl)elsevc_conflinletvc_list=[{vc_guard=Mark.copye(Mark.removevc_confl);vc_kind=NoOverlappingExceptions;(* Placeholder until we add all assertions in scope once
* we finished traversing it *)vc_asserts=trivial_asserte;vc_scope=ctx.current_scope_name;vc_variable=scope_let_var,scope_let.scope_let_pos;};]inletvc_list=matchscope_let.scope_let_kindwith|ScopeVarDefinition->letvc_empty=generate_vc_must_not_return_emptyctxeinletvc_empty=ifGlobals.optimize()thenExpr.unbox(Shared_ast.Optimizations.optimize_exprctx.declvc_empty)elsevc_emptyin{vc_guard=Mark.copye(Mark.removevc_empty);vc_kind=NoEmptyError;vc_asserts=trivial_asserte;vc_scope=ctx.current_scope_name;vc_variable=scope_let_var,scope_let.scope_let_pos;}::vc_list|_->vc_listinctx,vc_list,[]|_->ctx,[],[]inletnew_ctx,new_vcs,new_asserts=generate_verification_conditions_scope_body_expr{new_ctxwithscope_variables_typs=Var.Map.addscope_let_varscope_let.scope_let_typnew_ctx.scope_variables_typs;}scope_let_nextinnew_ctx,vc_list@new_vcs,assert_list@new_assertsletgenerate_verification_conditions_code_items(decl_ctx:decl_ctx)(code_items:'mexprcode_item_list)(s:ScopeName.toption):verification_conditionlist=letconditions,()=BoundList.fold_left~f:(funvcsitem_->matchitemwith|Topdef_->[]|ScopeDef(name,body)->letis_selected_scope=matchswith|SomeswhenScopeName.equalsname->true|None->true|_->falseinletnew_vcs=ifis_selected_scopethenlet_scope_input_var,scope_body_expr=Bindlib.unbindbody.scope_body_exprinletctx={current_scope_name=name;decl=decl_ctx;input_vars=[];scope_variables_typs=Var.Map.empty(* We don't need to add the typ of the scope input var here
because it will never appear in an expression for which
we generate a verification conditions (the big struct is
destructured with a series of let bindings just
after.) *);}inlet_,vcs,asserts=generate_verification_conditions_scope_body_exprctxscope_body_exprinletcombined_assert=conjunction_exprsasserts(Typed{pos=Pos.no_pos;ty=Mark.addPos.no_pos(TLitTBool)})inList.map(funvc->{vcwithvc_asserts=combined_assert})vcselse[]innew_vcs@vcs)~init:[]code_itemsinconditionsletgenerate_verification_conditions(p:'mprogram)(s:ScopeName.toption):verification_conditionlist=letvcs=generate_verification_conditions_code_itemsp.decl_ctxp.code_itemssin(* We sort this list by scope name and then variable name to ensure consistent
output for testing*)List.sort(funvc1vc2->letto_strvc=Format.asprintf"%s.%s"(Format.asprintf"%a"ScopeName.formatvc.vc_scope)(Bindlib.name_of(Mark.removevc.vc_variable))inString.compare(to_strvc1)(to_strvc2))vcs