12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037103810391040104110421043104410451046104710481049105010511052105310541055105610571058105910601061106210631064106510661067106810691070107110721073107410751076107710781079108010811082108310841085108610871088108910901091109210931094109510961097109810991100110111021103110411051106110711081109111011111112111311141115111611171118111911201121112211231124112511261127112811291130113111321133113411351136113711381139114011411142114311441145114611471148114911501151115211531154115511561157115811591160116111621163116411651166116711681169117011711172117311741175117611771178117911801181118211831184118511861187118811891190119111921193119411951196119711981199120012011202120312041205120612071208120912101211121212131214121512161217121812191220122112221223122412251226122712281229123012311232123312341235123612371238123912401241124212431244124512461247124812491250125112521253125412551256125712581259126012611262126312641265126612671268126912701271127212731274(**************************************************************************)(* *)(* OCaml *)(* *)(* Jeremy Yallop, University of Cambridge *)(* Gabriel Scherer, Project Parsifal, INRIA Saclay *)(* Alban Reynaud, ENS Lyon *)(* *)(* Copyright 2017 Jeremy Yallop *)(* Copyright 2018 Alban Reynaud *)(* Copyright 2018 INRIA *)(* *)(* All rights reserved. This file is distributed under the terms of *)(* the GNU Lesser General Public License version 2.1, with the *)(* special exception on linking described in the file LICENSE. *)(* *)(**************************************************************************)(** Static checking of recursive declarations
Some recursive definitions are meaningful
{[
let rec factorial = function 0 -> 1 | n -> n * factorial (n - 1)
let rec infinite_list = 0 :: infinite_list
]}
but some other are meaningless
{[
let rec x = x
let rec x = x+1
]}
Intuitively, a recursive definition makes sense when the body of the
definition can be evaluated without fully knowing what the recursive
name is yet.
In the [factorial] example, the name [factorial] refers to a function,
evaluating the function definition [function ...] can be done
immediately and will not force a recursive call to [factorial] -- this
will only happen later, when [factorial] is called with an argument.
In the [infinite_list] example, we can evaluate [0 :: infinite_list]
without knowing the full content of [infinite_list], but with just its
address. This is a case of productive/guarded recursion.
On the contrary, [let rec x = x] is unguarded recursion (the meaning
is undetermined), and [let rec x = x+1] would need the value of [x]
while evaluating its definition [x+1].
This file implements a static check to decide which definitions are
known to be meaningful, and which may be meaningless. In the general
case, we handle a set of mutually-recursive definitions
{[
let rec x1 = e1
and x2 = e2
...
and xn = en
]}
Our check (see function [is_valid_recursive_expression] is defined
using two criteria:
Usage of recursive variables: how does each of the [e1 .. en] use the
recursive variables [x1 .. xn]?
Static or dynamic size: for which of the [ei] can we compute the
in-memory size of the value without evaluating [ei] (so that we can
pre-allocate it, and thus know its final address before evaluation).
The "static or dynamic size" is decided by the classify_* functions below.
The "variable usage" question is decided by a static analysis looking
very much like a type system. The idea is to assign "access modes" to
variables, where an "access mode" [m] is defined as either
m ::= Ignore (* the value is not used at all *)
| Delay (* the value is not needed at definition time *)
| Guard (* the value is stored under a data constructor *)
| Return (* the value result is directly returned *)
| Dereference (* full access and inspection of the value *)
The access modes of an expression [e] are represented by a "context"
[G], which is simply a mapping from variables (the variables used in
[e]) to access modes.
The core notion of the static check is a type-system-like judgment of
the form [G |- e : m], which can be interpreted as meaning either of:
- If we are allowed to use the variables of [e] at the modes in [G]
(but not more), then it is safe to use [e] at the mode [m].
- If we want to use [e] at the mode [m], then its variables are
used at the modes in [G].
In practice, for a given expression [e], our implementation takes the
desired mode of use [m] as *input*, and returns a context [G] as
*output*, which is (uniquely determined as) the most permissive choice
of modes [G] for the variables of [e] such that [G |- e : m] holds.
*)openAsttypesopenTypedtreeopenTypesexceptionIllegal_expr(** {1 Static or dynamic size} *)typesd=Static|Dynamicletis_ref:Types.value_description->bool=function|{Types.val_kind=Types.Val_prim{Primitive.prim_name="%makemutable";prim_arity=1}}->true|_->false(* See the note on abstracted arguments in the documentation for
Typedtree.Texp_apply *)letis_abstracted_arg:arg_label*expressionoption->bool=function|(_,None)->true|(_,Some_)->falseletclassify_expression:Typedtree.expression->sd=(* We need to keep track of the size of expressions
bound by local declarations, to be able to predict
the size of variables. Compare:
let rec r =
let y = fun () -> r ()
in y
and
let rec r =
let y = if Random.bool () then ignore else fun () -> r ()
in y
In both cases the final address of `r` must be known before `y` is compiled,
and this is only possible if `r` has a statically-known size.
The first definition can be allowed (`y` has a statically-known
size) but the second one is unsound (`y` has no statically-known size).
*)letrecclassify_expressionenve=matche.exp_descwith(* binding and variable cases *)|Texp_let(rec_flag,vb,e)->letenv=classify_value_bindingsrec_flagenvvbinclassify_expressionenve|Texp_ident(path,_,_)->classify_pathenvpath(* non-binding cases *)|Texp_open(_,e)|Texp_letmodule(_,_,_,_,e)|Texp_sequence(_,e)|Texp_letexception(_,e)->classify_expressionenve|Texp_construct(_,{cstr_tag=Cstr_unboxed},[e])->classify_expressionenve|Texp_construct_->Static|Texp_record{representation=Record_unboxed_;fields=[|_,Overridden(_,e)|]}->classify_expressionenve|Texp_record_->Static|Texp_apply({exp_desc=Texp_ident(_,_,vd)},_)whenis_refvd->Static|Texp_apply(_,args)whenList.existsis_abstracted_argargs->Static|Texp_apply_->Dynamic|Texp_for_|Texp_constant_|Texp_new_|Texp_instvar_|Texp_tuple_|Texp_array_|Texp_variant_|Texp_setfield_|Texp_while_|Texp_setinstvar_|Texp_pack_|Texp_object_|Texp_function_|Texp_lazy_|Texp_unreachable|Texp_hole|Texp_extension_constructor_->Static|Texp_match_|Texp_ifthenelse_|Texp_send_|Texp_field_|Texp_assert_|Texp_try_|Texp_override_|Texp_letop_->Dynamicandclassify_value_bindingsrec_flagenvbindings=(* We use a non-recursive classification, classifying each
binding with respect to the old environment
(before all definitions), even if the bindings are recursive.
Note: computing a fixpoint in some way would be more
precise, as the following could be allowed:
let rec topdef =
let rec x = y and y = fun () -> topdef ()
in x
*)ignorerec_flag;letold_env=envinletadd_value_bindingenvvb=matchvb.vb_pat.pat_descwith|Tpat_var(id,_loc)->letsize=classify_expressionold_envvb.vb_exprinIdent.addidsizeenv|_->(* Note: we don't try to compute any size for complex patterns *)envinList.fold_leftadd_value_bindingenvbindingsandclassify_pathenv=function|Path.Pidentx->begintryIdent.find_samexenvwithNot_found->(* an identifier will be missing from the map if either:
- it is a non-local identifier
(bound outside the letrec-binding we are analyzing)
- or it is bound by a complex (let p = e in ...) local binding
- or it is bound within a module (let module M = ... in ...)
that we are not traversing for size computation
For non-local identifiers it might be reasonable (although
not completely clear) to consider them Static (they have
already been evaluated), but for the others we must
under-approximate with Dynamic.
This could be fixed by a more complete implementation.
*)Dynamicend|Path.Pdot_|Path.Papply_|Path.Pextra_ty_->(* local modules could have such paths to local definitions;
classify_expression could be extend to compute module
shapes more precisely *)Dynamicinclassify_expressionIdent.empty(** {1 Usage of recursive variables} *)moduleMode=struct(** For an expression in a program, its "usage mode" represents
static information about how the value produced by the expression
will be used by the context around it. *)typet=|Ignore(** [Ignore] is for subexpressions that are not used at all during
the evaluation of the whole program. This is the mode of
a variable in an expression in which it does not occur. *)|Delay(** A [Delay] context can be fully evaluated without evaluating its argument
, which will only be needed at a later point of program execution. For
example, [fun x -> ?] or [lazy ?] are [Delay] contexts. *)|Guard(** A [Guard] context returns the value as a member of a data structure,
for example a variant constructor or record. The value can safely be
defined mutually-recursively with their context, for example in
[let rec li = 1 :: li].
When these subexpressions participate in a cyclic definition,
this definition is productive/guarded.
The [Guard] mode is also used when a value is not dereferenced,
it is returned by a sub-expression, but the result of this
sub-expression is discarded instead of being returned.
For example, the subterm [?] is in a [Guard] context
in [let _ = ? in e] and in [?; e].
When these subexpressions participate in a cyclic definition,
they cannot create a self-loop.
*)|Return(** A [Return] context returns its value without further inspection.
This value cannot be defined mutually-recursively with its context,
as there is a risk of self-loop: in [let rec x = y and y = x], the
two definitions use a single variable in [Return] context. *)|Dereference(** A [Dereference] context consumes, inspects and uses the value
in arbitrary ways. Such a value must be fully defined at the point
of usage, it cannot be defined mutually-recursively with its context. *)letequal=((=):t->t->bool)(* Lower-ranked modes demand/use less of the variable/expression they qualify
-- so they allow more recursive definitions.
Ignore < Delay < Guard < Return < Dereference
*)letrank=function|Ignore->0|Delay->1|Guard->2|Return->3|Dereference->4(* Returns the more conservative (highest-ranking) mode of the two
arguments.
In judgments we write (m + m') for (join m m').
*)letjoinmm'=ifrankm>=rankm'thenmelsem'(* If x is used with the mode m in e[x], and e[x] is used with mode
m' in e'[e[x]], then x is used with mode m'[m] (our notation for
"compose m' m") in e'[e[x]].
Return is neutral for composition: m[Return] = m = Return[m].
Composition is associative and [Ignore] is a zero/annihilator for
it: (compose Ignore m) and (compose m Ignore) are both Ignore. *)letcomposem'm=matchm',mwith|Ignore,_|_,Ignore->Ignore|Dereference,_->Dereference|Delay,_->Delay|Guard,Return->Guard|Guard,((Dereference|Guard|Delay)asm)->m|Return,Return->Return|Return,((Dereference|Guard|Delay)asm)->mendtypemode=Mode.t=Ignore|Delay|Guard|Return|DereferencemoduleEnv:sigtypetvalsingle:Ident.t->Mode.t->t(** Create an environment with a single identifier used with a given mode.
*)valempty:t(** An environment with no used identifiers. *)valfind:Ident.t->t->Mode.t(** Find the mode of an identifier in an environment. The default mode is
Ignore. *)valunguarded:t->Ident.tlist->Ident.tlist(** unguarded e l: the list of all identifiers in l that are dereferenced or
returned in the environment e. *)valdependent:t->Ident.tlist->Ident.tlist(** dependent e l: the list of all identifiers in l that are used in e
(not ignored). *)valjoin:t->t->tvaljoin_list:tlist->t(** Environments can be joined pointwise (variable per variable) *)valcompose:Mode.t->t->t(** Environment composition m[G] extends mode composition m1[m2]
by composing each mode in G pointwise *)valremove:Ident.t->t->t(** Remove an identifier from an environment. *)valtake:Ident.t->t->Mode.t*t(** Remove an identifier from an environment, and return its mode *)valremove_list:Ident.tlist->t->t(** Remove all the identifiers of a list from an environment. *)valequal:t->t->boolend=structmoduleM=Map.Make(Ident)(** A "t" maps each rec-bound variable to an access status *)typet=Mode.tM.tletequal=M.equalMode.equalletfind(id:Ident.t)(tbl:t)=tryM.findidtblwithNot_found->Ignoreletempty=M.emptyletjoin(x:t)(y:t)=M.fold(fun(id:Ident.t)(v:Mode.t)(tbl:t)->letv'=findidtblinM.addid(Mode.joinvv')tbl)xyletjoin_listli=List.fold_leftjoinemptyliletcomposemenv=M.map(Mode.composem)envletsingleidmode=M.addidmodeemptyletunguardedenvli=List.filter(funid->Mode.rank(findidenv)>Mode.rankGuard)liletdependentenvli=List.filter(funid->Mode.rank(findidenv)>Mode.rankIgnore)liletremove=M.removelettakeidenv=(findidenv,removeidenv)letremove_listlenv=List.fold_left(funenvid->M.removeidenv)envlendletremove_patpatenv=Env.remove_list(pat_bound_identspat)envletremove_patlistpatsenv=List.fold_rightremove_patpatsenv(* Usage mode judgments.
There are two main groups of judgment functions:
- Judgments of the form "G |- ... : m"
compute the environment G of a subterm ... from its mode m, so
the corresponding function has type [... -> Mode.t -> Env.t].
We write [... -> term_judg] in this case.
- Judgments of the form "G |- ... : m -| G'"
correspond to binding constructs (for example "let x = e" in the
term "let x = e in body") that have both an exterior environment
G (the environment of the whole term "let x = e in body") and an
interior environment G' (the environment at the "in", after the
binding construct has introduced new names in scope).
For example, let-binding could be given the following rule:
G |- e : m + m'
-----------------------------------
G+G' |- (let x = e) : m -| x:m', G'
Checking the whole term composes this judgment
with the "G |- e : m" form for the let body:
G |- (let x = e) : m -| G'
G' |- body : m
-------------------------------
G |- let x = e in body : m
To this judgment "G |- e : m -| G'" our implementation gives the
type [... -> Mode.t -> Env.t -> Env.t]: it takes the mode and
interior environment as inputs, and returns the exterior
environment.
We write [... -> bind_judg] in this case.
*)typeterm_judg=Mode.t->Env.ttypebind_judg=Mode.t->Env.t->Env.tletoption:'a.('a->term_judg)->'aoption->term_judg=funfom->matchowith|None->Env.empty|Somev->fvmletlist:'a.('a->term_judg)->'alist->term_judg=funflim->List.fold_left(funenvitem->Env.joinenv(fitemm))Env.emptyliletarray:'a.('a->term_judg)->'aarray->term_judg=funfarm->Array.fold_left(funenvitem->Env.joinenv(fitemm))Env.emptyarletsingle:Ident.t->term_judg=Env.singleletremove_id:Ident.t->term_judg->term_judg=funidfm->Env.removeid(fm)letremove_ids:Ident.tlist->term_judg->term_judg=funidsfm->Env.remove_listids(fm)letjoin:term_judglist->term_judg=funlim->Env.join_list(List.map(funf->fm)li)letempty=fun_->Env.empty(* A judgment [judg] takes a mode from the context as input, and
returns an environment. The judgment [judg << m], given a mode [m']
from the context, evaluates [judg] in the composed mode [m'[m]]. *)let(<<):term_judg->Mode.t->term_judg=funfinner_mode->funouter_mode->f(Mode.composeouter_modeinner_mode)(* A binding judgment [binder] expects a mode and an inner environment,
and returns an outer environment. [binder >> judg] computes
the inner environment as the environment returned by [judg]
in the ambient mode. *)let(>>):bind_judg->term_judg->term_judg=funbindertermmode->bindermode(termmode)(* Expression judgment:
G |- e : m
where (m) is an input of the code and (G) is an output;
in the Prolog mode notation, this is (+G |- -e : -m).
*)letrecexpression:Typedtree.expression->term_judg=funexp->matchexp.exp_descwith|Texp_ident(pth,_,_)->pathpth|Texp_let(rec_flag,bindings,body)->(*
G |- <bindings> : m -| G'
G' |- body : m
-------------------------------
G |- let <bindings> in body : m
*)value_bindingsrec_flagbindings>>expressionbody|Texp_letmodule(x,_,_,mexp,e)->module_binding(x,mexp)>>expressione|Texp_match(e,cases,_)->(*
(Gi; mi |- pi -> ei : m)^i
G |- e : sum(mi)^i
----------------------------------------------
G + sum(Gi)^i |- match e with (pi -> ei)^i : m
*)(funmode->letpat_envs,pat_modes=List.split(List.map(func->casecmode)cases)inletenv_e=expressione(List.fold_leftMode.joinIgnorepat_modes)inEnv.join_list(env_e::pat_envs))|Texp_for(_,_,low,high,_,body)->(*
G1 |- low: m[Dereference]
G2 |- high: m[Dereference]
G3 |- body: m[Guard]
---
G1 + G2 + G3 |- for _ = low to high do body done: m
*)join[expressionlow<<Dereference;expressionhigh<<Dereference;expressionbody<<Guard;]|Texp_constant_->empty|Texp_new(pth,_,_)->(*
G |- c: m[Dereference]
-----------------------
G |- new c: m
*)pathpth<<Dereference|Texp_instvar(self_path,pth,_inst_var)->join[pathself_path<<Dereference;pathpth]|Texp_apply({exp_desc=Texp_ident(_,_,vd)},[_,Somearg])whenis_refvd->(*
G |- e: m[Guard]
------------------
G |- ref e: m
*)expressionarg<<Guard|Texp_apply(e,args)->letarg(_,eo)=optionexpressioneoinletapp_mode=ifList.existsis_abstracted_argargsthen(* see the comment on Texp_apply in typedtree.mli;
the non-abstracted arguments are bound to local
variables, which corresponds to a Guard mode. *)GuardelseDereferenceinjoin[expressione;listargargs]<<app_mode|Texp_tupleexprs->listexpressionexprs<<Guard|Texp_arrayexprs->(*
let array_mode = match Typeopt.array_kind exp with
| Lambda.Pfloatarray ->
(* (flat) float arrays unbox their elements *)
Dereference
| Lambda.Pgenarray ->
(* This is counted as a use, because constructing a generic array
involves inspecting to decide whether to unbox (PR#6939). *)
Dereference
| Lambda.Paddrarray | Lambda.Pintarray ->
(* non-generic, non-float arrays act as constructors *)
Guard
in
*)letarray_mode=(* FIXME MERLIN this is incorrect, but it won't report false positive, so it
will do for now. *)Guardinlistexpressionexprs<<array_mode|Texp_construct(_,desc,exprs)->letaccess_constructor=matchdesc.cstr_tagwith|Cstr_extension(pth,_)->pathpth<<Dereference|_->emptyinletm'=matchdesc.cstr_tagwith|Cstr_unboxed->Return|Cstr_constant_|Cstr_block_|Cstr_extension_->Guardinjoin[access_constructor;listexpressionexprs<<m']|Texp_variant(_,eo)->(*
G |- e: m[Guard]
------------------ -----------
G |- `A e: m [] |- `A: m
*)optionexpressioneo<<Guard|Texp_record{fields=es;extended_expression=eo;representation=rep}->letfield_mode=matchrepwith|Record_float->Dereference|Record_unboxed_->Return|Record_regular|Record_inlined_|Record_extension_->Guardinletfield(_label,field_def)=matchfield_defwithKept_->empty|Overridden(_,e)->expressioneinjoin[arrayfieldes<<field_mode;optionexpressioneo<<Dereference]|Texp_ifthenelse(cond,ifso,ifnot)->(*
Gc |- c: m[Dereference]
G1 |- e1: m
G2 |- e2: m
---
Gc + G1 + G2 |- if c then e1 else e2: m
Note: `if c then e1 else e2` is treated in the same way as
`match c with true -> e1 | false -> e2`
*)join[expressioncond<<Dereference;expressionifso;optionexpressionifnot;]|Texp_setfield(e1,_,_,e2)->(*
G1 |- e1: m[Dereference]
G2 |- e2: m[Dereference]
---
G1 + G2 |- e1.x <- e2: m
Note: e2 is dereferenced in the case of a field assignment to
a record of unboxed floats in that case, e2 evaluates to
a boxed float and it is unboxed on assignment.
*)join[expressione1<<Dereference;expressione2<<Dereference;]|Texp_sequence(e1,e2)->(*
G1 |- e1: m[Guard]
G2 |- e2: m
--------------------
G1 + G2 |- e1; e2: m
Note: `e1; e2` is treated in the same way as `let _ = e1 in e2`
*)join[expressione1<<Guard;expressione2;]|Texp_while(cond,body)->(*
G1 |- cond: m[Dereference]
G2 |- body: m[Guard]
---------------------------------
G1 + G2 |- while cond do body done: m
*)join[expressioncond<<Dereference;expressionbody<<Guard;]|Texp_send(e1,_)->(*
G |- e: m[Dereference]
---------------------- (plus weird 'eo' option)
G |- e#x: m
*)join[expressione1<<Dereference]|Texp_field(e,_,_)->(*
G |- e: m[Dereference]
-----------------------
G |- e.x: m
*)expressione<<Dereference|Texp_setinstvar(pth,_,_,e)->(*
G |- e: m[Dereference]
----------------------
G |- x <- e: m
*)join[pathpth<<Dereference;expressione<<Dereference;]|Texp_letexception({ext_id},e)->(* G |- e: m
----------------------------
G |- let exception A in e: m
*)remove_idext_id(expressione)|Texp_assert(e,_)->(*
G |- e: m[Dereference]
-----------------------
G |- assert e: m
Note: `assert e` is treated just as if `assert` was a function.
*)expressione<<Dereference|Texp_packmexp->(*
G |- M: m
----------------
G |- module M: m
*)modexpmexp|Texp_object(clsstrct,_)->class_structureclsstrct|Texp_try(e,cases)->(*
G |- e: m (Gi; _ |- pi -> ei : m)^i
--------------------------------------------
G + sum(Gi)^i |- try e with (pi -> ei)^i : m
Contrarily to match, the patterns p do not inspect
the value of e, so their mode does not influence the
mode of e.
*)letcase_envcm=fst(casecm)injoin[expressione;listcase_envcases;]|Texp_override(pth,fields)->(*
G |- pth : m (Gi |- ei : m[Dereference])^i
----------------------------------------------------
G + sum(Gi)^i |- {< (xi = ei)^i >} (at path pth) : m
Note: {< .. >} is desugared to a function application, but
the function implementation might still use its arguments in
a guarded way only -- intuitively it should behave as a constructor.
We could possibly refine the arguments' Dereference into Guard here.
*)letfield(_,_,arg)=expressionarginjoin[pathpth<<Dereference;listfieldfields<<Dereference;]|Texp_function{cases}->(*
(Gi; _ |- pi -> ei : m[Delay])^i
--------------------------------------
sum(Gi)^i |- function (pi -> ei)^i : m
Contrarily to match, the value that is pattern-matched
is bound locally, so the pattern modes do not influence
the final environment.
*)letcase_envcm=fst(casecm)inlistcase_envcases<<Delay|Texp_lazye->(*
G |- e: m[Delay]
---------------- (modulo some subtle compiler optimizations)
G |- lazy e: m
*)letlazy_mode=matchTypeopt.classify_lazy_argumentewith|`Constant_or_function|`Identifier_|`Float_that_cannot_be_shortcut->Return|`Other->Delayinexpressione<<lazy_mode|Texp_letop{let_;ands;body;_}->letcase_envcm=fst(casecm)injoin[listbinding_op(let_::ands)<<Dereference;case_envbody<<Delay]|Texp_unreachable->(*
----------
[] |- .: m
*)empty|Texp_hole->empty|Texp_extension_constructor(_lid,pth)->pathpth<<Dereference|Texp_open(od,e)->open_declarationod>>expressioneandbinding_op:Typedtree.binding_op->term_judg=funbop->join[pathbop.bop_op_path;expressionbop.bop_exp]andclass_structure:Typedtree.class_structure->term_judg=funcs->listclass_fieldcs.cstr_fieldsandclass_field:Typedtree.class_field->term_judg=funcf->matchcf.cf_descwith|Tcf_inherit(_,ce,_super,_inh_vars,_inh_meths)->class_exprce<<Dereference|Tcf_val(_lab,_mut,_,cfk,_)->class_field_kindcfk|Tcf_method(_,_,cfk)->class_field_kindcfk|Tcf_constraint_->empty|Tcf_initializere->expressione<<Dereference|Tcf_attribute_->emptyandclass_field_kind:Typedtree.class_field_kind->term_judg=funcfk->matchcfkwith|Tcfk_virtual_->empty|Tcfk_concrete(_,e)->expressione<<Dereferenceandmodexp:Typedtree.module_expr->term_judg=funmexp->matchmexp.mod_descwith|Tmod_ident(pth,_)->pathpth|Tmod_structures->structures|Tmod_functor(_,e)->modexpe<<Delay|Tmod_apply(f,p,_)->join[modexpf<<Dereference;modexpp<<Dereference;]|Tmod_apply_unitf->modexpf<<Dereference|Tmod_constraint(mexp,_,_,coe)->letreccoercioncoek=matchcoewith|Tcoerce_none->kReturn|Tcoerce_structure_|Tcoerce_functor_->(* These coercions perform a shallow copy of the input module,
by creating a new module with fields obtained by accessing
the same fields in the input module. *)kDereference|Tcoerce_primitive_->(* This corresponds to 'external' declarations,
and the coercion ignores its argument *)kIgnore|Tcoerce_alias(_,pth,coe)->(* Alias coercions ignore their arguments, but they evaluate
their alias module 'pth' under another coercion. *)coercioncoe(funm->pathpth<<m)incoercioncoe(funm->modexpmexp<<m)|Tmod_unpack(e,_)->expressione|Tmod_hole->fun_->Env.empty(* G |- pth : m *)andpath:Path.t->term_judg=(*
------------
x: m |- x: m
G |- A: m[Dereference]
-----------------------
G |- A.x: m
G1 |- A: m[Dereference]
G2 |- B: m[Dereference]
------------------------ (as for term application)
G1 + G2 |- A(B): m
*)funpth->matchpthwith|Path.Pidentx->singlex|Path.Pdot(t,_)->patht<<Dereference|Path.Papply(f,p)->join[pathf<<Dereference;pathp<<Dereference;]|Path.Pextra_ty(p,_extra)->pathp(* G |- struct ... end : m *)andstructure:Typedtree.structure->term_judg=(*
G1, {x: _, x in vars(G1)} |- item1: G2 + ... + Gn in m
G2, {x: _, x in vars(G2)} |- item2: G3 + ... + Gn in m
...
Gn, {x: _, x in vars(Gn)} |- itemn: [] in m
---
(G1 + ... + Gn) - V |- struct item1 ... itemn end: m
*)funsm->List.fold_right(funitenv->structure_itemitmenv)s.str_itemsEnv.empty(* G |- <structure item> : m -| G'
where G is an output and m, G' are inputs *)andstructure_item:Typedtree.structure_item->bind_judg=funsmenv->matchs.str_descwith|Tstr_eval(e,_)->(*
Ge |- e: m[Guard]
G |- items: m -| G'
---------------------------------
Ge + G |- (e;; items): m -| G'
The expression `e` is treated in the same way as let _ = e
*)letjudg_e=expressione<<GuardinEnv.join(judg_em)env|Tstr_value(rec_flag,bindings)->value_bindingsrec_flagbindingsmenv|Tstr_module{mb_id;mb_expr}->module_binding(mb_id,mb_expr)menv|Tstr_recmodulembs->letbindings=List.map(fun{mb_id;mb_expr}->(mb_id,mb_expr))mbsinrecursive_module_bindingsbindingsmenv|Tstr_primitive_->env|Tstr_type_->(*
-------------------
G |- type t: m -| G
*)env|Tstr_typext{tyext_constructors=exts;_}->letext_ids=List.map(fun{ext_id=id;_}->id)extsinEnv.join(listextension_constructorextsm)(Env.remove_listext_idsenv)|Tstr_exception{tyexn_constructor=ext;_}->Env.join(extension_constructorextm)(Env.removeext.ext_idenv)|Tstr_modtype_|Tstr_class_type_|Tstr_attribute_->env|Tstr_openod->open_declarationodmenv|Tstr_classclasses->letclass_ids=letclass_id({ci_id_class=id;_},_)=idinList.mapclass_idclassesinletclass_declaration({ci_expr;_},_)m=Env.remove_listclass_ids(class_exprci_exprm)inEnv.join(listclass_declarationclassesm)(Env.remove_listclass_idsenv)|Tstr_include{incl_mod=mexp;incl_type=mty;_}->letincluded_ids=List.mapTypes.signature_item_idmtyinEnv.join(modexpmexpm)(Env.remove_listincluded_idsenv)(* G |- module M = E : m -| G *)andmodule_binding:(Ident.toption*Typedtree.module_expr)->bind_judg=fun(id,mexp)menv->(*
GE |- E: m[mM + Guard]
-------------------------------------
GE + G |- module M = E : m -| M:mM, G
*)letjudg_E,env=matchidwith|None->modexpmexp<<Guard,env|Someid->letmM,env=Env.takeidenvinletjudg_E=modexpmexp<<(Mode.joinmMGuard)injudg_E,envinEnv.join(judg_Em)envandopen_declaration:Typedtree.open_declaration->bind_judg=fun{open_expr=mexp;open_bound_items=sg;_}menv->letjudg_E=modexpmexpinletbound_ids=List.mapTypes.signature_item_idsginEnv.join(judg_Em)(Env.remove_listbound_idsenv)andrecursive_module_bindings:(Ident.toption*Typedtree.module_expr)list->bind_judg=funm_bindingsmenv->letmids=List.filter_mapfstm_bindingsinletbinding(mid,mexp)m=letjudg_E=matchmidwith|None->modexpmexp<<Guard|Somemid->letmM=Env.findmidenvinmodexpmexp<<(Mode.joinmMGuard)inEnv.remove_listmids(judg_Em)inEnv.join(listbindingm_bindingsm)(Env.remove_listmidsenv)andclass_expr:Typedtree.class_expr->term_judg=funce->matchce.cl_descwith|Tcl_ident(pth,_,_)->pathpth<<Dereference|Tcl_structurecs->class_structurecs|Tcl_fun(_,_,args,ce,_)->letids=List.mapfstargsinremove_idsids(class_exprce<<Delay)|Tcl_apply(ce,args)->letarg(_label,eo)=optionexpressioneoinjoin[class_exprce<<Dereference;listargargs<<Dereference;]|Tcl_let(rec_flag,bindings,_,ce)->value_bindingsrec_flagbindings>>class_exprce|Tcl_constraint(ce,_,_,_,_)->class_exprce|Tcl_open(_,ce)->class_exprceandextension_constructor:Typedtree.extension_constructor->term_judg=funec->matchec.ext_kindwith|Text_decl_->empty|Text_rebind(pth,_lid)->pathpth(* G |- let (rec?) (pi = ei)^i : m -| G' *)andvalue_bindings:rec_flag->Typedtree.value_bindinglist->bind_judg=funrec_flagbindingsmodebound_env->letall_bound_pats=List.map(funvb->vb.vb_pat)bindingsinletouter_env=remove_patlistall_bound_patsbound_envinletbindings_env=matchrec_flagwith|Nonrecursive->(*
(Gi, pi:_ |- ei : m[mbody_i])^i (pi : mbody_i -| D)^i
------------------------------------------------------------
Sum(Gi) + (D - (pi)^i) |- let (pi=ei)^i : m -| D
*)letbinding_env{vb_pat;vb_expr;_}m=letm'=Mode.composem(patternvb_patbound_env)inremove_patvb_pat(expressionvb_exprm')inlistbinding_envbindingsmode|Recursive->(*
(Gi, (xj : mdef_ij)^j |- ei : m[mbody_i])^i (xi : mbody_i -| D)^i
G'i = Gi + mdef_ij[G'j]
-------------------------------------------------------------------
Sum(G'i) + (D - (pi)^i) |- let rec (xi=ei)^i : m -| D
The (mdef_ij)^i,j are a family of modes over two indices:
mdef_ij represents the mode of use, within e_i the definition of x_i,
of the mutually-recursive variable x_j.
The (G'i)^i are defined from the (Gi)^i as a family of equations,
whose smallest solution is computed as a least fixpoint.
The (Gi)^i are the "immediate" dependencies of each (ei)^i
on the outer context (excluding the mutually-defined
variables).
The (G'i)^i contain the "transitive" dependencies as well:
if ei depends on xj, then the dependencies of G'i of xi
must contain the dependencies of G'j, composed by
the mode mdef_ij of use of xj in ei.
For example, consider:
let rec z =
let rec x = ref y
and y = ref z
in f x
this definition should be rejected as the body [f x]
dereferences [x], which can be used to access the
yet-unitialized value [z]. This requires realizing that [x]
depends on [z] through [y], which requires the transitive
closure computation.
An earlier version of our check would take only the (Gi)^i
instead of the (G'i)^i, which is incorrect and would accept
the example above.
*)(* [binding_env] takes a binding (x_i = e_i)
and computes (Gi, (mdef_ij)^j). *)letbinding_env{vb_pat=x_i;vb_expr=e_i;_}=letmbody_i=patternx_ibound_envin(* Gi, (x_j:mdef_ij)^j *)letrhs_env_i=expressione_i(Mode.composemodembody_i)in(* (mdef_ij)^j (for a fixed i) *)letmutual_modes=letmdef_ij{vb_pat=x_j;_}=patternx_jrhs_env_iinList.mapmdef_ijbindingsin(* Gi *)letenv_i=remove_patlistall_bound_patsrhs_env_iin(* (Gi, (mdef_ij)^j) *)(env_i,mutual_modes)inletenv,mdef=List.split(List.mapbinding_envbindings)inletrectransitive_closureenv=lettransitive_depsenv_imdef_i=(* Gi, (mdef_ij)^j => Gi + Sum_j mdef_ij[Gj] *)Env.joinenv_i(Env.join_list(List.map2Env.composemdef_ienv))inletenv'=List.map2transitive_depsenvmdefinifList.for_all2Env.equalenvenv'thenenv'elsetransitive_closureenv'inletenv'_i=transitive_closureenvinEnv.join_listenv'_iinEnv.joinbindings_envouter_env(* G; m' |- (p -> e) : m
with outputs G, m' and input m
m' is the mode under which the scrutinee of p
(the value matched against p) is placed.
*)andcase:'k.'kTypedtree.case->mode->Env.t*mode=fun{Typedtree.c_lhs;c_guard;c_rhs}->(*
Ge |- e : m Gg |- g : m[Dereference]
G := Ge+Gg p : mp -| G
----------------------------------------
G - p; m[mp] |- (p (when g)? -> e) : m
*)letjudg=join[optionexpressionc_guard<<Dereference;expressionc_rhs;]in(funm->letenv=judgmin(remove_patc_lhsenv),Mode.composem(patternc_lhsenv))(* p : m -| G
with output m and input G
m is the mode under which the scrutinee of p is placed.
*)andpattern:typek.kgeneral_pattern->Env.t->mode=funpatenv->(*
mp := | Dereference if p is destructuring
| Guard otherwise
me := sum{G(x), x in vars(p)}
--------------------------------------------
p : (mp + me) -| G
*)letm_pat=ifis_destructuring_patternpatthenDereferenceelseGuardinletm_env=pat_bound_identspat|>List.map(funid->Env.findidenv)|>List.fold_leftMode.joinIgnoreinMode.joinm_patm_envandis_destructuring_pattern:typek.kgeneral_pattern->bool=funpat->matchpat.pat_descwith|Tpat_any->false|Tpat_var(_,_)->false|Tpat_alias(pat,_,_)->is_destructuring_patternpat|Tpat_constant_->true|Tpat_tuple_->true|Tpat_construct_->true|Tpat_variant_->true|Tpat_record(_,_)->true|Tpat_array_->true|Tpat_lazy_->true|Tpat_valuepat->is_destructuring_pattern(pat:>pattern)|Tpat_exception_->false|Tpat_or(l,r,_)->is_destructuring_patternl||is_destructuring_patternrletis_valid_recursive_expressionidlistexpr=matchexpr.exp_descwith|Texp_function_->(* Fast path: functions can never have invalid recursive references *)true|_->matchclassify_expressionexprwith|Static->(* The expression has known size *)letty=expressionexprReturninEnv.unguardedtyidlist=[]|Dynamic->(* The expression has unknown size *)letty=expressionexprReturninEnv.unguardedtyidlist=[]&&Env.dependenttyidlist=[](* A class declaration may contain let-bindings. If they are recursive,
their validity will already be checked by [is_valid_recursive_expression]
during type-checking. This function here prevents a different kind of
invalid recursion, which is the unsafe creations of objects of this class
in the let-binding. For example,
{|class a = let x = new a in object ... end|}
is forbidden, but
{|class a = let x () = new a in object ... end|}
is allowed.
*)letis_valid_class_expridlistce=letrecclass_expr:mode->Typedtree.class_expr->Env.t=funmodece->matchce.cl_descwith|Tcl_ident(_,_,_)->(*
----------
[] |- a: m
*)Env.empty|Tcl_structure_->(*
-----------------------
[] |- struct ... end: m
*)Env.empty|Tcl_fun(_,_,_,_,_)->Env.empty(*
---------------------------
[] |- fun x1 ... xn -> C: m
*)|Tcl_apply(_,_)->Env.empty|Tcl_let(rec_flag,bindings,_,ce)->value_bindingsrec_flagbindingsmode(class_exprmodece)|Tcl_constraint(ce,_,_,_,_)->class_exprmodece|Tcl_open(_,ce)->class_exprmodeceinmatchEnv.unguarded(class_exprReturnce)idlistwith|[]->true|_::_->false