123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826(*---------------------------------------------------------------------------
Copyright (c) 2026 Invariant Systems. All rights reserved.
Portions adapted from Bisect_ppx (MIT license).
SPDX-License-Identifier: ISC
---------------------------------------------------------------------------*)(* Overview
This is the core of windtrap coverage: the instrumenter that runs on ASTs is
defined here. The instrumenter is divided into two major pieces:
1. The class [instrumenter] traverses ASTs. It decides where instrumentation
should be inserted.
2. The module [Generated_code] provides the helpers that actually insert the
instrumentation. In other words, they insert new leaves into the AST at
the places chosen by [instrumenter].
The code is structured to strongly reflect this division. It is recommended
to read this file with code folding.
Instrumented locations are called {e points}. When the instrumentation code
is executed, the point is {e visited}. Points appear as highlighted
characters in coverage reports.
All state is contained within instances of [instrumenter].
Instances are created in [register.ml], which is the "top-level"
side-effecting module. *)moduleParsetree=Ppxlib.ParsetreemoduleLocation=Ppxlib.LocationmoduleAst_builder=Ppxlib.Ast_buildermoduleLongident=Ppxlib.LongidentmodulePat=Ppxlib.Ast_helper.PatmoduleExp=Ppxlib.Ast_helper.ExpmoduleStr=Ppxlib.Ast_helper.StrmoduleCl=Ppxlib.Ast_helper.ClmoduleCf=Ppxlib.Ast_helper.CfmoduleCoverage_attributes:sigvalrecognize:Parsetree.attribute->[`None|`On|`Off|`Exclude_file]valhas_off_attribute:Parsetree.attributes->boolvalhas_exclude_file_attribute:Parsetree.structure->boolend=structletrecognize{Parsetree.attr_name;attr_payload;attr_loc}=ifattr_name.txt<>"coverage"then`Noneelsematchattr_payloadwith|Parsetree.PStr[{pstr_desc=Pstr_eval({pexp_desc=Pexp_ident{txt=Longident.Lidents;_};_},_);_;};]->(matchswith|"off"->`Off|"on"->`On|"exclude_file"->`Exclude_file|_->Location.raise_errorf~loc:attr_loc"Bad payload in coverage attribute.")|_->Location.raise_errorf~loc:attr_loc"Bad payload in coverage attribute."lethas_off_attributeattributes=(* Don't short-circuit the search, because we want to error-check all
attributes. *)List.fold_left(funfound_offattribute->matchrecognizeattributewith|`None->found_off|`Off->true|`On->Location.raise_errorf~loc:attribute.attr_loc"coverage on is not allowed here."|`Exclude_file->Location.raise_errorf~loc:attribute.attr_loc"coverage exclude_file is not allowed here.")falseattributeslethas_exclude_file_attributestructure=structure|>List.exists(function|{Parsetree.pstr_desc=Pstr_attributeattribute;_}whenrecognizeattribute=`Exclude_file->true|_->false)endmoduleGenerated_code:sigtypepointsvalinit:unit->pointsvalinstrument_expr:points->?override_loc:Location.t->?use_loc_of:Parsetree.expression->?at_end:bool->?post:bool->Parsetree.expression->Parsetree.expressionvalinstrument_cases:points->Parsetree.caselist->Parsetree.caselistvalruntime_initialization:points->string->Parsetree.structure_itemlistend=structtypepoints={mutableoffsets:intlist;mutablecount:int}letinit()={offsets=[];count=0}(* Given an AST for an expression [e], replaces it by the sequence expression
[instrumentation; e], where [instrumentation] is some code that tells
the runtime, at runtime, that [e] has been visited. *)letinstrument_exprpoints?override_loc?use_loc_of?(at_end=false)?(post=false)e=letrecoutline()=letloc=choose_location_of_point~override_loc~use_loc_ofeinifexpression_should_not_be_instrumented~point_loc:loc~use_loc_oftheneelseletpoint_index=get_index_of_point_at_location~point_loc:locinletopenParsetreeinifnotpostthen[%expr___windtrap_visit___[%epoint_index];[%ee]]else[%expr___windtrap_post_visit___[%epoint_index][%ee]]andchoose_location_of_point~override_loc~use_loc_ofe=matchuse_loc_ofwith|Somee->Parsetree.(e.pexp_loc)|None->(matchoverride_locwith|Someoverride_loc->override_loc|_->Parsetree.(e.pexp_loc))andexpression_should_not_be_instrumented~point_loc:loc~use_loc_of=lete=matchuse_loc_ofwithSomee->e|None->einLocation.(loc.loc_ghost)||Coverage_attributes.has_off_attributee.pexp_attributesandget_index_of_point_at_location~point_loc:loc=letpoint_offset=ifnotat_endthenLocation.(Lexing.(loc.loc_start.pos_cnum))elseLocation.(Lexing.(loc.loc_end.pos_cnum-1))inletpoint=letrecfind_pointpointsoffsetindexoffsets=matchoffsetswith|offset'::_whenoffset'=offset->index|_::rest->find_pointpointsoffset(index-1)rest|[]->letindex=points.countinpoints.offsets<-offset::points.offsets;points.count<-points.count+1;indexinfind_pointpointspoint_offset(points.count-1)points.offsetsinAst_builder.Default.eint~locpointinoutline()(* Simplified case instrumentation: instrument each case's guard and
right-hand side without or-pattern rotation. *)letcase_should_not_be_instrumented(case:Parsetree.case)=matchcase.pc_rhswith|[%exprassertfalse]->true|{pexp_desc=Pexp_unreachable;_}->true|{pexp_attributes;_}whenCoverage_attributes.has_off_attributepexp_attributes->true|_->falseletinstrument_casespoints(cases:Parsetree.caselist)=List.map(fun(case:Parsetree.case)->ifcase_should_not_be_instrumentedcasethencaseelsematchcase.pc_guardwith|None->{casewithpc_rhs=instrument_exprpointscase.pc_rhs}|Someguard->{casewithpc_guard=Some(instrument_exprpointsguard);pc_rhs=instrument_exprpointscase.pc_rhs;})casesletruntime_initializationpointsfile=letloc=Location.in_filefileinletmangled_module_name=letbuffer=Buffer.create(String.lengthfile*2)infile|>String.iter(function|('A'..'Z'|'a'..'z'|'0'..'9'|'_')asc->Buffer.add_charbufferc|_->Buffer.add_stringbuffer"___");"Windtrap_cov___"^Buffer.contentsbufferinletpoints_data=Ast_builder.Default.pexp_array~loc(List.map(funoffset->Ast_builder.Default.eint~locoffset)(List.revpoints.offsets))inletfilename=Ast_builder.Default.estring~locfilein(* ___windtrap_visit___ is a function with a reference to a point count
array. It is called every time a point is visited.
It is scoped in a local module, to ensure that each compilation unit
calls its own ___windtrap_visit___ function. In particular, if
___windtrap_visit___ is unscoped, a later [open] can shadow it.
To prevent this, we generate:
module Windtrap_cov___<mangled_filename> =
struct
let ___windtrap_visit___ = (* ... *)
end
open Windtrap_cov___<mangled_filename>
Module names are mangled to be unique per file, avoiding duplicate
module errors when one file includes another. *)letgenerated_module=letvisit_function=letopenParsetreein[%strilet___windtrap_visit___=letpoints=[%epoints_data]inlet(`Visitvisit)=Windtrap_coverage.register_file~filename:[%efilename]~pointsinvisit]inletpost_visit=letopenParsetreein[%strilet___windtrap_post_visit___point_indexresult=___windtrap_visit___point_index;result]inletopenPpxlib.Ast_helperinStr.module_~loc@@Mb.mk~loc{txt=Somemangled_module_name;loc}(Mod.structure~loc[visit_function;post_visit])inletmodule_open=letopenPpxlib.Ast_helperinStr.open_~loc@@Opn.mk~loc@@Mod.ident~loc{txt=Longident.parsemangled_module_name;loc}inletopenParsetreeinletstop_comment=[%stri[@@@ocaml.text"/*"]]in[stop_comment;generated_module;module_open;stop_comment]endlet(>>=)=Ppxlib.With_errors.(>>=)let(>>|)=Ppxlib.With_errors.(>>|)letcollect_errors=Ppxlib.With_errors.combine_errorsletreturn=Ppxlib.With_errors.return(* The actual "instrumenter" object, instrumenting expressions. *)classinstrumenter=letpoints=Generated_code.init()inletinstrument_expr=Generated_code.instrument_exprpointsinletinstrument_cases=Generated_code.instrument_casespointsinobject(self)inheritPpxlib.Ast_traverse.map_with_expansion_context_and_errorsassupermethod!class_exprctxtce=letloc=ce.pcl_locinletattrs=ce.pcl_attributesinsuper#class_exprctxtce>>|funce->matchce.pcl_descwith|Pcl_fun(l,e,p,ce)->Cl.fun_~loc~attrsl(Option.mapinstrument_expre)pce|_->cemethod!class_fieldctxtcf=letloc=cf.pcf_locinletattrs=cf.pcf_attributesinsuper#class_fieldctxtcf>>|funcf->matchcf.pcf_descwith|Pcf_method(name,private_,cf)->Cf.method_~loc~attrsnameprivate_(matchcfwith|Cfk_virtual_->cf|Cfk_concrete(o,e)->Cf.concreteo(instrument_expre))|Pcf_initializere->Cf.initializer_~loc~attrs(instrument_expre)|_->cfmethod!expressionctxte=letis_trivial_function=Parsetree.(function|[%expr(&&)]|[%expr(&)]|[%exprnot]|[%expr(=)]|[%expr(<>)]|[%expr(<)]|[%expr(<=)]|[%expr(>)]|[%expr(>=)]|[%expr(==)]|[%expr(!=)]|[%exprref]|[%expr(!)]|[%expr(:=)]|[%expr(@)]|[%expr(^)]|[%expr(+)]|[%expr(-)]|[%expr(*)]|[%expr(/)]|[%expr(+.)]|[%expr(-.)]|[%expr(*.)]|[%expr(/.)]|[%expr(mod)]|[%expr(land)]|[%expr(lor)]|[%expr(lxor)]|[%expr(lsl)]|[%expr(lsr)]|[%expr(asr)]|[%exprraise]|[%exprraise_notrace]|[%exprfailwith]|[%exprignore]|[%exprSys.opaque_identity]|[%exprObj.magic]|[%expr(##)]->true|_->false)inletrectraverse?(successor=`None)~is_in_tail_positione=letattrs=e.Parsetree.pexp_attributesinifCoverage_attributes.has_off_attributeattrsthenreturneelsebeginletloc=e.pexp_locinmatche.pexp_descwith(* Expressions that invoke arbitrary code, and may not terminate. *)|Pexp_apply((([%expr(|>)]|[%expr(|.)])asoperator),[(l,e);(l',e')])->traverse~successor:(`Expressione')~is_in_tail_position:falsee>>=fune_traversed->traverse~successor:`Redundant~is_in_tail_position:falsee'>>|fune'_traversed->letapply=Exp.apply~loc~attrsoperator[(l,e_traversed);(l',e'_traversed)]inifis_in_tail_positionthenapplyelsebeginmatchsuccessorwith|`None->letrecfne'=matche'.Parsetree.pexp_descwith|Pexp_apply(e'',_)->letattributes=e'.pexp_attributesinifCoverage_attributes.has_off_attributeattributesthene'elsefne''|_->e'ininstrument_expr~use_loc_of:(fne')~at_end:true~post:trueapply|`Redundant->apply|`Expressione->instrument_expr~use_loc_of:e~post:trueapplyend|Pexp_apply(([%expr(||)]|[%expr(or)]),[(_l,e);(_l',e')])->lete_mark=instrument_expr~use_loc_of:e~at_end:true[%exprtrue]inbeginmatche'.pexp_descwith|Pexp_apply(([%expr(||)]|[%expr(or)]),_)->traverse~is_in_tail_positione'|Pexp_apply(e'',_)whenis_in_tail_position&¬(is_trivial_functione'')->traverse~is_in_tail_position:truee'|(Pexp_send_|Pexp_new_)whenis_in_tail_position->traverse~is_in_tail_position:truee'|_->traverse~is_in_tail_position:falsee'>>|funcondition->letopenParsetreein[%exprif[%econdition]then[%einstrument_expr~use_loc_of:e'~at_end:true[%exprtrue]]elsefalse]end>>=fune'_new->letopenParsetreeintraverse~is_in_tail_position:falsee>>|fune_new->[%exprif[%ee_new]then[%ee_mark]else[%ee'_new]]|Pexp_apply(e,arguments)->beginmatch(e,arguments)with|([%expr(&&)]|[%expr(&)]),[(ll,el);(lr,er)]->traverse~is_in_tail_position:falseel>>=funel_new->traverse~is_in_tail_positioner>>|funer_new->[(ll,el_new);(lr,instrument_exprer_new)]|([%expr(@@)],[(ll,({pexp_desc=Pexp_apply_;_}asel));(lr,er)])->traverse~successor:`Redundant~is_in_tail_position:falseel>>=funel_new->traverse~is_in_tail_position:falseer>>|funer_new->[(ll,el_new);(lr,er_new)]|_->arguments|>List.map(fun(label,e)->traverse~is_in_tail_position:falsee>>|fune_new->(label,e_new))|>collect_errorsend>>=funarguments->beginmatche.pexp_descwith|Pexp_new_->returne|Pexp_send_->traverse~successor:`Redundant~is_in_tail_position:falsee|_->traverse~is_in_tail_position:falseeend>>|fune->letapply=Exp.apply~loc~attrseargumentsinletall_arguments_labeled=arguments|>List.for_all(fun(label,_)->label<>Ppxlib.Nolabel)inifis_in_tail_position||all_arguments_labeledthenapplyelseifis_trivial_functionethenapplyelsebeginmatchsuccessorwith|`None->letuse_loc_of=match(e,arguments)with|[%expr(@@)],[(_,e');_]->e'|_->eininstrument_expr~use_loc_of~at_end:true~post:trueapply|`Redundant->apply|`Expressione'->instrument_expr~use_loc_of:e'~at_end:false~post:trueapplyend|Pexp_send(e,m)->traverse~is_in_tail_position:falsee>>|fune_new->letapply=Exp.send~loc~attrse_newminifis_in_tail_positionthenapplyelsebeginmatchsuccessorwith|`None->instrument_expr~at_end:true~post:trueapply|`Redundant->apply|`Expressione'->instrument_expr~use_loc_of:e'~post:trueapplyend|Pexp_new_->return@@ifis_in_tail_positiontheneelsebeginmatchsuccessorwith|`None->instrument_expr~at_end:true~post:truee|`Redundant->e|`Expressione'->instrument_expr~use_loc_of:e'~post:trueeend|Pexp_assert[%exprfalse]->returne|Pexp_asserte->traverse~is_in_tail_position:falsee>>|fune_new->instrument_expr~use_loc_of:e~post:true(Exp.assert_e_new)(* Expressions that have subexpressions that might not get visited. *)(* ppxlib 0.37.0: Pexp_function (params, constraint_, body)
where body = Pfunction_body expr | Pfunction_cases (cases, loc, attrs)
Pexp_fun no longer exists; it is represented as Pexp_function
with a single Pparam_val parameter and Pfunction_body. *)|Pexp_function(params,constraint_,body)->beginmatchbodywith|Pfunction_bodybody_expr->traverse~is_in_tail_position:truebody_expr>>|funbody_expr->letbody_expr=matchbody_expr.pexp_descwith|Pexp_function_->body_expr|Pexp_constraint(inner,t)->{body_exprwithpexp_desc=Pexp_constraint(instrument_exprinner,t);}|_->(* Don't instrument if this is an intermediate fun param
(i.e. there are further params making it a chain of funs).
We detect "leaf" bodies by checking that there are params. *)ifparams<>[]theninstrument_exprbody_exprelsebody_exprin{ewithpexp_desc=Pexp_function(params,constraint_,Pfunction_bodybody_expr);}|Pfunction_cases(cases,cases_loc,cases_attrs)->traverse_cases~is_in_tail_position:truecases>>|funcases_new->letcases_instrumented=instrument_casescases_newin{ewithpexp_desc=Pexp_function(params,constraint_,Pfunction_cases(cases_instrumented,cases_loc,cases_attrs));}end|Pexp_match(e,cases)->traverse_cases~is_in_tail_positioncases>>=funcases->letcases=instrument_casescasesintraverse~successor:`Redundant~is_in_tail_position:falsee>>|fune->Exp.match_~loc~attrsecases|Pexp_try(e,cases)->traverse_cases~is_in_tail_positioncases>>=funcases->letcases=instrument_casescasesintraverse~is_in_tail_position:falsee>>|fune->Exp.try_~loc~attrsecases|Pexp_ifthenelse(if_,then_,else_)->traverse~successor:`Redundant~is_in_tail_position:falseif_>>=funif_->traverse~is_in_tail_positionthen_>>=funthen_->beginmatchelse_with|None->returnNone|Someelse_->traverse~is_in_tail_positionelse_>>|funelse_->Some(instrument_exprelse_)end>>|funelse_->Exp.ifthenelse~loc~attrsif_(instrument_exprthen_)else_|Pexp_while(while_,do_)->traverse~is_in_tail_position:falsewhile_>>=funwhile_->traverse~is_in_tail_position:falsedo_>>|fundo_->Exp.while_~loc~attrswhile_(instrument_exprdo_)|Pexp_for(v,init,to_,direction,do_)->traverse~is_in_tail_position:falseinit>>=funinit->traverse~is_in_tail_position:falseto_>>=funto_->traverse~is_in_tail_position:falsedo_>>|fundo_->Exp.for_~loc~attrsvinitto_direction(instrument_exprdo_)|Pexp_lazye->letrecis_trivial_syntactic_valuee=matche.Parsetree.pexp_descwith|Pexp_function_|Pexp_poly_|Pexp_ident_|Pexp_constant_|Pexp_construct(_,None)->true|Pexp_constraint(e,_)|Pexp_coerce(e,_,_)->is_trivial_syntactic_valuee|_->falseintraverse~is_in_tail_position:truee>>|fune->lete=(* lazy applied to certain syntactic values is compiled as already
forced. Since inserting instrumentation under such a lazy would
make the nested expression not a syntactic value, it would
change the compilation of the lazy. *)ifis_trivial_syntactic_valueetheneelseinstrument_expreinExp.lazy_~loc~attrse|Pexp_poly(e,t)->traverse~is_in_tail_position:truee>>|fune->lete=matche.pexp_descwith|Pexp_function_->e|_->instrument_expreinExp.poly~loc~attrset|Pexp_letop{let_;ands;body}->lettraverse_binding_opbinding_op=traverse~is_in_tail_position:falsebinding_op.Parsetree.pbop_exp>>|funpbop_exp->{binding_opwithParsetree.pbop_exp}intraverse_binding_oplet_>>=funlet_->List.maptraverse_binding_opands|>collect_errors>>=funands->traverse~is_in_tail_position:truebody>>|funbody->Exp.letop~loc~attrslet_ands(instrument_exprbody)(* Expressions that don't fit either of the above categories. These
don't need to be instrumented. *)|Pexp_ident_|Pexp_constant_->returne|Pexp_let(rec_flag,bindings,e)->letsuccessor=matchbindingswith[_one]->`Expressione|_->`Noneinbindings|>List.map(funbinding->traverse~successor~is_in_tail_position:falsebinding.Parsetree.pvb_expr>>|fune->Parsetree.{bindingwithpvb_expr=e})|>collect_errors>>=funbindings->traverse~is_in_tail_positione>>|fune->Exp.let_~loc~attrsrec_flagbindingse|Pexp_tuplees->List.map(traverse~is_in_tail_position:false)es|>collect_errors>>|funes->Exp.tuple~loc~attrses|Pexp_construct(c,e)->beginmatchewith|None->returnNone|Somee->traverse~is_in_tail_position:falsee>>|fune->Someeend>>|fune->Exp.construct~loc~attrsce|Pexp_variant(c,e)->beginmatchewith|None->returnNone|Somee->traverse~is_in_tail_position:falsee>>|fune->Someeend>>|fune->Exp.variant~loc~attrsce|Pexp_record(fields,e)->fields|>List.map(fun(f,e)->traverse~is_in_tail_position:falsee>>|fune->(f,e))|>collect_errors>>=funfields->beginmatchewith|None->returnNone|Somee->traverse~is_in_tail_position:falsee>>|fune->Someeend>>|fune->Exp.record~loc~attrsfieldse|Pexp_field(e,f)->traverse~is_in_tail_position:falsee>>|fune->Exp.field~loc~attrsef|Pexp_setfield(e,f,e')->traverse~is_in_tail_position:falsee>>=fune->traverse~is_in_tail_position:falsee'>>|fune'->Exp.setfield~loc~attrsefe'|Pexp_arrayes->List.map(traverse~is_in_tail_position:false)es|>collect_errors>>|funes->Exp.array~loc~attrses|Pexp_sequence(e,e')->traverse~is_in_tail_positione'>>=fune'->lete'=matche.pexp_descwith|Pexp_ifthenelse(_,_,None)->instrument_expre'|_->e'intraverse~successor:(`Expressione')~is_in_tail_position:falsee>>|fune->Exp.sequence~loc~attrsee'|Pexp_constraint(e,t)->traverse~is_in_tail_positione>>|fune->Exp.constraint_~loc~attrset|Pexp_coerce(e,t,t')->traverse~is_in_tail_positione>>|fune->Exp.coerce~loc~attrsett'|Pexp_setinstvar(f,e)->traverse~is_in_tail_position:falsee>>|fune->Exp.setinstvar~loc~attrsfe|Pexp_overridefs->fs|>List.map(fun(f,e)->traverse~is_in_tail_position:falsee>>|fune->(f,e))|>collect_errors>>|funfs->Exp.override~loc~attrsfs|Pexp_letmodule(m,e,e')->self#module_exprctxte>>=fune->traverse~is_in_tail_positione'>>|fune'->Exp.letmodule~loc~attrsmee'|Pexp_letexception(c,e)->traverse~is_in_tail_positione>>|fune->Exp.letexception~loc~attrsce|Pexp_open(m,e)->self#open_declarationctxtm>>=funm->traverse~is_in_tail_positione>>|fune->Exp.open_~loc~attrsme|Pexp_newtype(t,e)->traverse~is_in_tail_positione>>|fune->Exp.newtype~loc~attrste(* Expressions that don't need instrumentation, and where AST
traversal leaves the expression language. *)|Pexp_objectc->self#class_structurectxtc>>|func->Exp.object_~loc~attrsc|Pexp_packm->self#module_exprctxtm>>|funm->Exp.pack~loc~attrsm(* Expressions that are not recursively traversed at all. *)|Pexp_extension_|Pexp_unreachable->returneendandtraverse_cases~is_in_tail_positioncases=cases|>List.mapbeginfuncase->beginmatchcase.Parsetree.pc_guardwith|None->returnNone|Someguard->traverse~is_in_tail_position:falseguard>>|funguard->Someguardend>>=funpc_guard->traverse~is_in_tail_positioncase.pc_rhs>>|funpc_rhs->{casewithpc_guard;pc_rhs}end|>collect_errorsintraverse~is_in_tail_position:falsee(* Set to [true] upon encountering [[@@@coverage.off]], and back to
[false] again upon encountering [[@@@coverage.on]]. *)valmutablestructure_instrumentation_suppressed=falsemethod!structure_itemctxtsi=letloc=si.pstr_locinmatchsi.pstr_descwith|Pstr_value(rec_flag,bindings)->ifstructure_instrumentation_suppressedthenreturnsielsebindings|>List.mapbeginfun(binding:Parsetree.value_binding)->letdo_not_instrument=Coverage_attributes.has_off_attributebinding.pvb_attributesinifdo_not_instrumentthenreturnbindingelsebeginself#expressionctxtbinding.pvb_expr>>|fune->{bindingwithpvb_expr=e}endend|>collect_errors>>|funbindings->Str.value~locrec_flagbindings|Pstr_eval(e,a)->ifstructure_instrumentation_suppressedthenreturnsielsebeginself#expressionctxte>>|fune->Str.eval~loc~attrs:aeend|Pstr_attributeattribute->letkind=Coverage_attributes.recognizeattributeinbeginmatchkindwith|`None->()|`Off->ifstructure_instrumentation_suppressedthenLocation.raise_errorf~loc:attribute.attr_loc"Coverage is already off.";structure_instrumentation_suppressed<-true|`On->ifnotstructure_instrumentation_suppressedthenLocation.raise_errorf~loc:attribute.attr_loc"Coverage is already on.";structure_instrumentation_suppressed<-false|`Exclude_file->Location.raise_errorf~loc:attribute.attr_loc"coverage exclude_file is not allowed here."end;returnsi|_->super#structure_itemctxtsi(* Don't instrument payloads of extensions and attributes. *)method!extension_e=returnemethod!attribute_a=returnamethod!structurectxtast=letsaved_structure_instrumentation_suppressed=structure_instrumentation_suppressedinletresult=super#structurectxtastinstructure_instrumentation_suppressed<-saved_structure_instrumentation_suppressed;resultmethodtransform_impl_filectxtast=letsaved_structure_instrumentation_suppressed=structure_instrumentation_suppressedinletresult=letpath=Ppxlib.Expansion_context.Base.input_namectxtinletfile_should_not_be_instrumented=letalways_ignore_paths=["//toplevel//";"(stdin)"]inletalways_ignore_basenames=[".ocamlinit";"topfind"]inList.mempathalways_ignore_paths||List.mem(Filename.basenamepath)always_ignore_basenames||Coverage_attributes.has_exclude_file_attributeastiniffile_should_not_be_instrumentedthenastelsebeginletinstrumented_ast,errors=super#structurectxtastinleterrors=errors|>List.map(funerror->Ast_builder.Default.pstr_extension~loc:(Location.Error.get_locationerror)(Location.Error.to_extensionerror)[])inletruntime_initialization=Generated_code.runtime_initializationpointspathinerrors@runtime_initialization@instrumented_astendinstructure_instrumentation_suppressed<-saved_structure_instrumentation_suppressed;resultend