123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204openPpxlibopenAst_builder.Defaultletpartial_evar~locn=evar~loc("_partial"^string_of_int(n+1))letpartial_pvar~locn=pvar~loc("_partial"^string_of_int(n+1))letbindexprf=matchexpr.pexp_descwith|Pexp_ident_->fexpr|_->pexp_let~loc:expr.pexp_locNonrecursive[value_binding~loc:expr.pexp_loc~pat:(pvar~loc:expr.pexp_loc"_partial_fun")~expr:expr](f(evar~loc:expr.pexp_loc"_partial_fun"))letwilling_to_reexecutee=(* Things that incur no side effects, and no allocations (to avoid transforming one
allocation into arbitrarily many, so no pexp_function or pexp_tuple of trivial
things for instance). We must accept __, otherwise this will never fire. *)matchewith|{pexp_desc=(Pexp_ident_|Pexp_constant_|Pexp_construct(_,None));_}->true|_->falseletbeta_redex~locparamsbodyexprs=eapply~loc(eabstract~locparamsbody)exprsletwith_inferred_type_of_arg~loc(first_params,last_param)bodyfirst_args=(* If we're compiling [f e1 __ e3], we want type directed disambiguation to be
preserved, meaning to work the same as in [fun x -> f e1 x e2].
If we create the naive [let v1 = e1 and v3 = e3 in fun v2 -> f v1 v2 v3], then
[e1] and [e3] have no expected types, hence no type-directed disambiguation.
One reasonably simple improvement is to replace the let-binding by the OG
let-binding, meaning a beta-redex : [(fun v1 v3 v2 -> f v1 v2 v3) e1 e3] (note the
order of parameters). With this, [e1] and [e3] are typed according to what f
expects, but [e3] is still not typed according to what the context expects v2 to
be (in things like [List.map l ~f:..], provided List.map is defined nicely like
Base does but unlike what the Stdlib does, the type of the elements of l flows
into ~f's argument, and this is very useful).
So the next and last trick is to generate [(fun v1 v3 (v2 : 'v2) -> f v1 v2 v3) e1
e3 : 'v2 -> _], so the expected type of the resulting function flows into v2,
which flows into v3, which then flows into [e3]. That solution doesn't concretely
work, because adding an annotation (_ : 'fresh) around expressions can cause type
errors. So instead we use dead code that encodes the same flow of type inference. *)iffalse(* Maybe we should make this simpler version available as a flag *)thenbeta_redex~locfirst_params[%expr();fun[%plast_param]->[%ebody]]first_argselse[%exprlet_partial_arg_type=(* ('v2 -> unit) option, for a fresh non generalized 'v2.
We want contravariant in 'v2 so the variable is non
generalized, but we want to avoid function values, as
the closure middle end doesn't do a good job at
eliminating unused ones (even ignore doesn't work). *)iftruethen(None:(_->unit)option)elseStdlib.(!)(assertfalse)iniffalsethen(* unify 'v2 with the expected type of v2 *)(funx->(match_partial_arg_typewithNone->()|Somef->fx);assertfalse)else[%ebeta_redex~locfirst_params[%expriffalsethenfunx->(match_partial_arg_typewith|None->()|Somef->fx);assertfalseelsefun[%plast_param]->[%ebody]]first_args]]letrewrite~locfparams=(* If we have to process [f e1 __ e2] *)ifwilling_to_reexecutef&&List.for_all(fun(_,e)->willing_to_reexecutee)paramsthen(* generate cleaner code [fun x -> f e1 x e2] for trivial things like ~f:(__ + 1),
to guarantee good perf even if the compiler wouldn't optimize away our local
function, as is presumably the case in the bytecode -> js_of_ocaml pipeline. *)letreplace_placeholdere=matchewith|[%expr__]->partial_evar~loc:e.pexp_loc1|_->einletargs=List.map(fun(arg,e)->(arg,replace_placeholdere))paramsineabstract~loc[partial_pvar~loc1](pexp_apply~loc(replace_placeholderf)args)else(* Otherwise, generate [(fun p1 p3 p2 -> f p1 p2 p3) e1 e2] (roughly, see
with_inferred_type_of_arg for the gory details). *)bindf(funf->letremaining_params=List.filter_map(function|(_,[%expr__])->None|(_,e)->Somee)paramsinletbody=pexp_apply~loc(matchfwith|[%expr__]->partial_evar~loc:f.pexp_loc(-1)|_->f)(List.mapi(funi(arg,e)->arg,partial_evar~loc:e.pexp_loci)params)inletfirst_params,last_param=letlast_param=refNoneinletfirst_params=leti=ref(-1)inList.filter_map(fun(_arg,e)->i:=!i+1;letparam=partial_pvar~loc:e.pexp_loc!iinmatchewith|[%expr__]->last_param:=Someparam;None|_->Someparam)paramsinletlast_param=match!last_paramwith|None->partial_pvar~loc:f.pexp_loc(-1)|Somep->pinfirst_params,last_paraminwith_inferred_type_of_arg~loc(first_params,last_param)bodyremaining_params)let()=Driver.register_transformation~preprocess_impl:(funstr->(* We need preprocess_impl rather than impl so we run before ppx_pipebang, as
ppx_pipebang changes the arity of functions. ppxlib has this silly restriction
where only one ppx can run before the context-free ppxes, so this may make us
incompatible with other ppxes. *)objectinheritPpxlib_traverse_builtins.mapinheritmapassupermethod!expressione=lete=super#expressioneinmatchewith|{pexp_desc=Pexp_field([%expr__]asplaceholder,fieldname);_}->lete'=letloc=placeholder.pexp_locin{ewithpexp_desc=Pexp_field([%exprx],fieldname)}inletloc=e.pexp_locin[%exprfunx->[%ee']]|{pexp_desc=Pexp_construct(constructor,Some([%expr__]asplaceholder));_}->lete'=letloc=placeholder.pexp_locin{ewithpexp_desc=Pexp_construct(constructor,Some[%exprx])}inletloc=e.pexp_locin[%exprfunx->[%ee']]|{pexp_desc=Pexp_variant(constructor,Some([%expr__]asplaceholder));_}->lete'=letloc=placeholder.pexp_locin{ewithpexp_desc=Pexp_variant(constructor,Some[%exprx])}inletloc=e.pexp_locin[%exprfunx->[%ee']]|{pexp_desc=Pexp_apply(f,params);_}->letcount=List.fold_left(funacc(_,e)->matchewith|[%expr__]->acc+1|_->acc)(matchfwith|[%expr__]->1|_->0)paramsinifcount=0theneelseifcount>1thenletreplace_placeholdere=matchewith|[%expr__]->pexp_extension~loc:e.pexp_loc(Location.error_extensionf~loc:e.pexp_loc"ppx_partial: only one __ argument is supported per function call")|e->einletf=replace_placeholderfinletparams=List.map(fun(arg,e)->arg,replace_placeholdere)paramsin{ewithpexp_desc=Pexp_apply(f,params)}elserewrite~loc:e.pexp_locfparams|_->eend#structurestr)"ppx_partial"