12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037103810391040104110421043104410451046104710481049105010511052105310541055105610571058105910601061106210631064106510661067106810691070107110721073107410751076107710781079108010811082108310841085108610871088108910901091109210931094109510961097109810991100110111021103110411051106110711081109111011111112111311141115111611171118111911201121112211231124112511261127112811291130113111321133113411351136113711381139114011411142114311441145114611471148114911501151115211531154115511561157115811591160116111621163116411651166116711681169117011711172117311741175117611771178117911801181118211831184118511861187118811891190119111921193119411951196119711981199120012011202120312041205120612071208120912101211121212131214121512161217121812191220122112221223122412251226122712281229123012311232123312341235123612371238123912401241124212431244124512461247124812491250125112521253125412551256125712581259126012611262126312641265126612671268126912701271127212731274127512761277127812791280128112821283128412851286128712881289129012911292129312941295129612971298129913001301130213031304130513061307130813091310131113121313131413151316131713181319132013211322132313241325132613271328132913301331133213331334133513361337133813391340134113421343134413451346134713481349135013511352135313541355135613571358135913601361136213631364136513661367136813691370137113721373137413751376137713781379138013811382138313841385138613871388138913901391139213931394139513961397139813991400140114021403140414051406140714081409141014111412141314141415141614171418141914201421(**************************************************************************)(* *)(* 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, as described in
A practical mode system for recursive definitions
Alban Reynaud, Gabriel Scherer and Jeremy Yallop
POPL 2021
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.
*)openAsttypesopenTypedtreeopenTypes(** {1 Static or dynamic size} *)typesd=Value_rec_types.recursive_binding_kindletis_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:sd=matche.exp_descwith(* binding and variable cases *)|Texp_let(rec_flag,vb,e)->letenv=classify_value_bindingsrec_flagenvvbinclassify_expressionenve|Texp_letmodule(Somemid,_,_,mexp,e)->(* Note on module presence:
For absent modules (i.e. module aliases), the module being bound
does not have a physical representation, but its size can still be
derived from the alias itself, so we can re-use the same code as
for modules that are present. *)letsize=classify_module_expressionenvmexpinletenv=Ident.addmidsizeenvinclassify_expressionenve|Texp_ident(path,_,_)->classify_pathenvpath(* non-binding cases *)|Texp_open(_,e)|Texp_letmodule(None,_,_,_,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_variant_|Texp_tuple_|Texp_extension_constructor_|Texp_constant_->Static|Texp_for_|Texp_setfield_|Texp_while_|Texp_setinstvar_->(* Unit-returning expressions *)Static|Texp_unreachable->Static|Texp_apply({exp_desc=Texp_ident(_,_,vd)},_)whenis_refvd->Static|Texp_apply(_,args)whenList.existsis_abstracted_argargs->Static|Texp_apply_->Dynamic|Texp_array_->Static|Texp_packmexp->classify_module_expressionenvmexp|Texp_function_->Static|Texp_lazye->(* The code below was copied (in part) from translcore.ml *)beginmatchTypeopt.classify_lazy_argumentewith|`Constant_or_function->(* A constant expr (of type <> float if [Config.flat_float_array] is
true) gets compiled as itself. *)classify_expressionenve|`Float_that_cannot_be_shortcut|`Identifier`Forward_value->(* Forward blocks *)Static|`Identifier`Other->classify_expressionenve|`Other->(* other cases compile to a lazy block holding a function *)Staticend|Texp_new_|Texp_instvar_|Texp_object_|Texp_match_|Texp_ifthenelse_|Texp_send_|Texp_field_|Texp_assert_|Texp_try_|Texp_override_|Texp_letop_->Dynamic|Texp_hole->Staticandclassify_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,_uid)->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:_->Value_rec_types.recursive_binding_kind=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 Not_recursive.
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 *)Dynamicandclassify_module_expressionenvmexp:sd=matchmexp.mod_descwith|Tmod_hole->Dynamic|Tmod_ident(path,_)->classify_pathenvpath|Tmod_structure_->Static|Tmod_functor_->Static|Tmod_apply_->Dynamic|Tmod_apply_unit_->Dynamic|Tmod_constraint(mexp,_,_,coe)->beginmatchcoewith|Tcoerce_none->classify_module_expressionenvmexp|Tcoerce_structure_->Static|Tcoerce_functor_->Static|Tcoerce_primitive_->Misc.fatal_error"letrec: primitive coercion on a module"|Tcoerce_alias_->Misc.fatal_error"letrec: alias coercion on a module"end|Tmod_unpack(e,_)->classify_expressionenveinclassify_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)->(* [args] may contain omitted arguments, corresponding to labels in
the function's type that were not passed in the actual application.
The arguments before the first omitted argument are passed to the
function immediately, so they are dereferenced. The arguments after
the first omitted one are stored in a closure, so guarded.
The function itself is called immediately (dereferenced) if there
is at least one argument before the first omitted one.
On the other hand, if the first argument is omitted then the
function is stored in the closure without being called. *)letrecsplit_args~has_omitted_arg=function|[]->[],[]|(_,None)::rest->split_args~has_omitted_arg:truerest|(_,Somearg)::rest->letapplied,delayed=split_args~has_omitted_argrestinifhas_omitted_argthenapplied,arg::delayedelsearg::applied,delayedinletapplied,delayed=split_args~has_omitted_arg:falseargsinletfunction_mode=matchappliedwith|[]->Guard|_::_->Dereferenceinjoin[expressione<<function_mode;listexpressionapplied<<Dereference;listexpressiondelayed<<Guard]|Texp_tupleexprs->listexpressionexprs<<Guard|Texp_arrayexprs->letarray_mode=matchTypeopt.array_kindexpwith|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 *)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(params,body)->(*
G |-{body} b : m[Delay]
(Hj |-{def} Pj : m[Delay])^j
H := sum(Hj)^j
ps := sum(pat(Pj))^j
-----------------------------------
G + H - ps |- fun (Pj)^j -> b : m
*)letparam_patparam=(* param P ::=
| ?(pat = expr)
| pat
Define pat(P) as
pat if P = ?(pat = expr)
pat if P = pat
*)matchparam.fp_kindwith|Tparam_patpat->pat|Tparam_optional_default(pat,_)->patin(* Optional argument defaults.
G |-{def} P : m
*)letparam_defaultparam=matchparam.fp_kindwith|Tparam_optional_default(_,default)->(*
G |- e : m
------------------
G |-{def} ?(p=e) : m
*)expressiondefault|Tparam_pat_->(*
------------------
. |-{def} p : m
*)emptyinletpatterns=List.mapparam_patparamsinletdefaults=List.mapparam_defaultparamsinletbody=function_bodybodyinletf=join(body::defaults)<<Delayin(funm->letenv=fminremove_patlistpatternsenv)|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|Texp_hole->(*
----------
[] |- .: m
*)empty|Texp_extension_constructor(_lid,pth)->pathpth<<Dereference|Texp_open(od,e)->open_declarationod>>expressione(* Function bodies.
G |-{body} b : m
*)andfunction_bodybody=matchbodywith|Tfunction_bodybody->(*
G |- e : m
------------------
G |-{body} e : m (**)
(**) The "e" here stands for [Tfunction_body] as opposed to
[Tfunction_cases].
*)expressionbody|Tfunction_cases{cases;_}->(*
(Gi; _ |- pi -> ei : m)^i (**)
------------------
sum(Gi)^i |-{body} function (pi -> ei)^i : m
(**) Contrarily to match, the values that are pattern-matched
are bound locally, so the pattern modes do not influence
the final environment.
*)List.map(funcmode->fst(casecmode))cases|>joinandbinding_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:sdoption=matchexpr.exp_descwith|Texp_function_->(* Fast path: functions can never have invalid recursive references *)SomeStatic|_->letrkind=classify_expressionexprinletis_valid=matchrkindwith|Static->(* The expression has known size or is constant *)letty=expressionexprReturninEnv.unguardedtyidlist=[]|Dynamic->(* The expression has unknown size *)letty=expressionexprReturninEnv.unguardedtyidlist=[]&&Env.dependenttyidlist=[]inifis_validthenSomerkindelseNone(* 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