123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207(* This file is part of the Catala compiler, a specification language for tax
and social benefits computation rules. Copyright (C) 2020 Inria, contributor:
Alain Delaët <alain.delaet--tixeuil@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. *)openShared_astopenAstopenCatala_utilstypeinvariant_status=Fail|Pass|Ignoretypeinvariant_expr=decl_ctx->typedexpr->invariant_statusletcheck_invariant(inv:string*invariant_expr)(p:typedprogram):bool=letname,inv=invinletresult,total,ok=Program.fold_exprsp~init:(true,0,0)~f:(funacce_ty->(* let currente = e in *)letrecfe(result,total,ok)=letresult,total,ok=Expr.shallow_foldfe(result,total,ok)inmatchinvp.decl_ctxewith|Ignore->result,total,ok|Fail->Message.error~pos:(Expr.pose)"@[<v 2>Invariant @{<magenta>%s@} failed.@,%a@]"name(Print.expr())e|Pass->result,total+1,ok+1infeacc)inMessage.debug"Invariant %s checked.@ result: [%d/%d]"nameoktotal;result(* Structural invariant: no default can have as type A -> B *)letinvariant_default_no_arrow():string*invariant_expr=("default_no_arrow",fun_ctxe->matchMark.removeewith|EDefault_->beginmatchMark.remove(Expr.tye)withTArrow_->Fail|_->Passend|_->Ignore)(* Structural invariant: no partial evaluation *)letinvariant_no_partial_evaluation():string*invariant_expr=("no_partial_evaluation",fun_ctxe->matchMark.removeewith|EApp{f;args;_}->(matchMark.remove(Expr.tyf)with|TArrow(tl,_)whenList.lengthargs=List.lengthtl->Pass|_->Fail)|_->Ignore)(* Structural invariant: no function can return an function *)letinvariant_no_return_a_function():string*invariant_expr=("no_return_a_function",fun_ctxe->matchMark.removeewith|EAbs_->beginmatchMark.remove(Expr.tye)with|TArrow(_,(TArrow_,_))->Fail|_->Passend|_->Ignore)letinvariant_app_inversion():string*invariant_expr=("app_inversion",fun_ctxe->matchMark.removeewith|EApp{f=EAbs{binder;_},_;args;_}->ifBindlib.mbinder_aritybinder=List.lengthargsthenPasselseFail|EApp{f=EVar_,_;_}->Pass|EApp{f=EStructAccess_,_;_}->Pass|EApp{f=EExternal_,_;_}->Pass|EApp_->Fail|_->Ignore)(** the arity of constructors when matching is always one. *)letinvariant_match_inversion():string*invariant_expr=("match_inversion",fun_ctxe->matchMark.removeewith|EMatch{cases;_}->ifEnumConstructor.Map.for_all(fun_case->matchMark.removecasewith|EAbs{binder;_}->Bindlib.mbinder_aritybinder=1|_->false)casesthenPasselseFail|_->Ignore)(* The purpose of these functions is to determine whether the type `TDefault`
can only appear in certain positions, such as:
* On the left-hand side of an arrow with arity 1, as the type of a scope (for
scope calls).
* At the root of the type tree (outside a default).
* On the right-hand side of the arrow at the root of the type (occurs for
rentrant variables).
For instance, the following types do follow the invariant:
int; bool; int -> bool; <bool>; <int -> bool>; int -> <bool>; S_in {x: int ->
<bool>} -> S {y: bool}
While the following types does not follow the invariant:
<<int>>; <int -> <bool>>; <bool> -> int; S_in {x: int -> <bool>} -> S {y:
<bool>}
This is crucial to maintain the safety of the type system, as demonstrated in
the formal development. *)letreccheck_typ_no_defaultctxty=matchMark.removetywith|TLit_->true|TTuplets->List.for_all(check_typ_no_defaultctx)ts|TStructn->lets=StructName.Map.findnctx.ctx_structsinStructField.Map.for_all(fun_kty->check_typ_no_defaultctxty)s|TEnumn->lets=EnumName.Map.findnctx.ctx_enumsinEnumConstructor.Map.for_all(fun_kty->check_typ_no_defaultctxty)s|TOptionty->check_typ_no_defaultctxty|TArrow(args,res)->List.for_all(check_typ_no_defaultctx)args&&check_typ_no_defaultctxres|TArrayty->check_typ_no_defaultctxty|TDefault_t->false|TAny->Message.error~internal:true"Some Dcalc invariants are invalid: TAny was found whereas it should be \
fully resolved."|TClosureEnv->Message.error~internal:true"Some Dcalc invariants are invalid: TClosureEnv was found whereas it \
should only appear later in the compilation process."letcheck_type_thunked_or_nodefaultctxty=check_typ_no_defaultctxty||matchMark.removetywith|TArrow(args,res)->(List.for_all(check_typ_no_defaultctx)args&&matchMark.removereswith|TDefaultty->check_typ_no_defaultctxty|_->check_typ_no_defaultctxty)|_->falseletcheck_type_rootctxty=check_type_thunked_or_nodefaultctxty||matchMark.removetywith|TStructn->lets=StructName.Map.findnctx.ctx_structsinScopeName.Map.exists(fun_kinfo->StructName.equalinfo.in_struct_namen)ctx.ctx_scopes&&StructField.Map.for_all(fun_kty->check_type_thunked_or_nodefaultctxty)s|TArrow([(TStructn,_)],res)->lets=StructName.Map.findnctx.ctx_structsinScopeName.Map.exists(fun_kinfo->StructName.equalinfo.in_struct_namen)ctx.ctx_scopes&&StructField.Map.for_all(fun_kty->check_type_thunked_or_nodefaultctxty)s&&check_typ_no_defaultctxres|TDefaultarg->check_typ_no_defaultctxarg|_->falseletinvariant_typing_defaults():string*invariant_expr=("typing_defaults",functxe->ifcheck_type_rootctx(Expr.tye)thenPasselse(Message.warning"typing error %a@."(Print.typctx)(Expr.tye);Fail))letcheck_all_invariantsprgm=List.fold_left(&&)true[check_invariant(invariant_default_no_arrow())prgm;check_invariant(invariant_no_partial_evaluation())prgm;check_invariant(invariant_no_return_a_function())prgm;check_invariant(invariant_app_inversion())prgm;check_invariant(invariant_match_inversion())prgm;check_invariant(invariant_typing_defaults())prgm;]