123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691openBaseopenPpxlibopenAst_builder.DefaultmoduleInvariant=structletset_to_stringset=set|>Set.to_list|>List.map~f:(funfield_name->Printf.sprintf"`%s'"field_name)|>String.concat~sep:", ";;letall_disjoints~loc~add~remove~modify~set=letcheck(n1,s1)(n2,s2)=letcommon=Set.inters1s2inifnot(Set.is_emptycommon)thenLocation.raise_errorf~loc"Sets `%s' and `%s' must be disjoint but they are not: %s found in both"n1n2(set_to_stringcommon)inleta="add",addinletb="remove",removeinletc="modify",modifyinletd="set",setincheckab;checkac;checkad;checkbc;checkbd;checkcd;;letthings_are_known~loc~all~thing_name~supposed_to_beto_remove=letunknown_fields=Set.diffto_removeallinifnot(Set.is_emptyunknown_fields)then(letstr=set_to_stringunknown_fieldsinLocation.raise_errorf~loc"Some %s were supposed to be %s but they were not found: %s"thing_namesupposed_to_bestr);;end(* {1 Some helper functions} *)letname_of_type_name~dir~source~type_name=matchtype_name.ptyp_descwith|Ptyp_constr({txt=type_name;_},_)->letfun_name=Printf.sprintf"%s_%s"(matchdirwith|`To->"to"|`Of->"of")(String.concat~sep:"_"(Longident.flatten_exntype_name))in(matchsourcewith|"t"->fun_name|_->source^"_"^fun_name)|_->assertfalse;;letstable_variant_name~type_name=matchtype_namewith|"t"->"Stable_variant"|_->"Stable_variant_of_"^type_name;;letstable_variants~type_=matchtype_.ptyp_descwith|Ptyp_constr({txt=type_;_},_)->lettype_name=List.hd_exn(List.rev(Longident.flatten_exntype_))instable_variant_name~type_name|_->assertfalse;;letmodify_field_namename="modify_"^nameletremove_field_namename="remove_"^nameletmk_lident~locstr=Located.mk~loc(Longident.Lidentstr)letmk_module~loc~name~items=pstr_module~loc(module_binding~loc~name:(Located.mk~loc(Somename))~expr:(pmod_structure~locitems));;(* fun ~name:name -> exp *)letmk_pexp_fun~loc~nameexp=pexp_fun~loc(Labelledname)None(pvar~locname)expletrecurse_name="recurse"letmap_if_recursive~loc~rec_flagtype_name=matchrec_flagwith|Recursive->Map.singleton(moduleString)type_name(evar~locrecurse_name)|Nonrecursive->Map.empty(moduleString);;(* we only need to mark the function recursive if we made any recursive calls. the type
might say recursive (the default) without actually being recursive *)letset_any_recursive_and_return_expr~any_recursive(result,expr)=match(result:Generic_map.replace_result)with|Unchanged->expr|Replaced->any_recursive:=true;expr;;(* This is complicated so here's some help on fields:
- source = fields from record [@@deriving stable_record] attached to
- target = source + add - remove
- add:[ a ] = value is gong to come from ~a argument
- set:[ s ] = value is going to come from ~s argument
- modify:[ m ] = value is going to come from ~modify_m argument
In particular:
fields_from_args = set + (target - source) = set + (add - remove)
are the fields that we expect to get from ~a and ~s arguments.
*)letconvert_record~loc~fields~source_fields~target_fields~modified_fields~set_fields~source_type~target_type~rec_flag~type_name=letrecord_pat=letrecord_pat=List.map(Set.to_listsource_fields)~f:(funname->ifSet.memtarget_fieldsname&¬(Set.memset_fieldsname)thenmk_lident~locname,ppat_var~loc(Located.mk~locname)elsemk_lident~locname,ppat_any~loc)inppat_record~locrecord_patClosedinletfields_from_args=Set.unionset_fields(Set.difftarget_fieldssource_fields)inletany_recursive=reffalseinletfields=Map.of_alist_exn(moduleString)(List.mapfields~f:(funld->ld.pld_name.txt,ld))inletmap_if_recursive=map_if_recursive~loc~rec_flagtype_nameinlettarget_record=letfields=List.map(Set.to_listtarget_fields)~f:(funname->letexpr=ifSet.memmodified_fieldsnamethen(letf=evar~loc(modify_field_namename)inpexp_apply~locf[Nolabel,evar~locname])elseifSet.memfields_from_argsnamethenevar~locnameelse(letld=Map.find_exnfieldsnameinGeneric_map.build~loc~map:map_if_recursiveld.pld_type(evar~locname)|>set_any_recursive_and_return_expr~any_recursive)inmk_lident~locname,expr)inpexp_record~locfieldsNoneinletacc=match!any_recursivewith|false->[%exprlet([%precord_pat]:[%tsource_type])=_tin([%etarget_record]:[%ttarget_type])]|true->[%exprletrec[%ppvar~locrecurse_name]=fun([%precord_pat]:[%tsource_type]):[%ttarget_type]->[%etarget_record]in[%eevar~locrecurse_name]_t]inletacc=Set.foldfields_from_args~init:acc~f:(funaccname->mk_pexp_fun~loc~nameacc)inletacc=Set.fold_rightmodified_fields~init:acc~f:(funnameacc->letname=modify_field_namenameinmk_pexp_fun~loc~nameacc)in(* we put this argument first to help with record field disambiguation at the use site *)[%exprfun(_t:[%tsource_type])->[%eacc]];;letcd_args_and_value~loc~tuple_opt~record~fcd=matchcd.pcd_argswith|Pcstr_tupletys->letargs,pats=List.mapitys~f:(funity->letvar="v"^Int.to_stringiin(Nolabel,var),ftyvar)|>List.unzipinargs,tuple_optpats|Pcstr_recordlds->letargs,pats=List.mapilds~f:(funild->letvar="v"^Int.to_stringiin((Labelledld.pld_name.txt,var),(Located.lident~locld.pld_name.txt,fld.pld_typevar)))|>List.unzipinargs,Some(recordpats);;letgenerate_stable_variant_module~td~loc~cdl=letalias_fun_labelcd=String.lowercasecd.pcd_name.txt^"_fun"inletmap_function=letcases=List.mapcdl~f:(funcd->letargs,pattern=cd_args_and_valuecd~loc~tuple_opt:(ppat_tuple_opt~loc)~record:(funp->ppat_record~locpClosed)~f:(fun_x->pvar~locx)inletpattern=ppat_construct~loc(Located.lident~loccd.pcd_name.txt)patterninletvalue=letfun_expr=evar~loc(alias_fun_labelcd)inifList.is_emptyargsthen[%expr[%efun_expr]()]elseList.mapargs~f:(fun(lbl,x)->lbl,evar~locx)|>pexp_apply~locfun_exprincase~guard:None~lhs:pattern~rhs:value)inletexpr=List.fold_right~init:(pexp_function~loccases)cdl~f:(funcdacc->letname=String.lowercasecd.pcd_name.txtinpexp_fun~loc(Labelledname)None(pvar~loc(alias_fun_labelcd))acc)inmk_module~loc~name:"Helper"~items:[pstr_value~locNonrecursive[value_binding~loc~pat:(pvar~loc"map")~expr]]in[mk_module~loc~name:(stable_variant_name~type_name:td.ptype_name.txt)~items:[map_function]];;letconvert_variant~loc~constructors~source_variants~target_variants~modified_variants~target_type~source_type~rec_flag~type_name=(* Create pexp_ident scoped to the same module as [which_type]. *)letvariants_longident~loc~which_typepath=letaddlongident_optx=matchlongident_optwith|Somel->Some(Ldot(l,x))|None->Some(Lidentx)inletinit=matchwhich_type.ptyp_descwith|Ptyp_constr(lid_loc,_)->(matchlid_loc.txtwith|Lapply_->Location.raise_errorf~loc"Unexpected Lapply"|Lident_->None|Ldot(t,_)->Somet)|_->assertfalseinletlongident=Option.value_exn(List.fold~initpath~f:add)inLocated.mk~loclongidentinletconstructors=Map.of_alist_exn(moduleString)(List.mapconstructors~f:(funcd->cd.pcd_name.txt,cd))inletany_recursive=reffalseinletmap_if_recursive=map_if_recursive~loc~rec_flagtype_nameinletmap_cdcd=letargs,value=cd_args_and_valuecd~loc~tuple_opt:(pexp_tuple_opt~loc)~record:(fune->pexp_record~loceNone)~f:(funtyalias->Generic_map.build~loc~map:map_if_recursivety(evar~localias)|>set_any_recursive_and_return_expr~any_recursive)inletvalue=pexp_construct~loc(variants_longident~which_type:target_type~loc[cd.pcd_name.txt])valueinifList.is_emptyargsthen[%exprfun()->[%evalue]]elseList.fold_rightargs~init:value~f:(fun(label,alias)acc->pexp_fun~loclabelNone(pvar~localias)acc)inletacc=letmap_fn=variants_longident~loc~which_type:source_type[stable_variants~type_:source_type;"Helper";"map"]in[%expr[%epexp_ident~locmap_fn]v]inletrhs=Set.foldsource_variants~init:acc~f:(funaccname->letf=ifSet.memmodified_variantsnamethenevar~loc(modify_field_namename)elseifnot(Set.memtarget_variantsname)thenevar~loc(remove_field_namename)elsemap_cd(Map.find_exnconstructorsname)inpexp_apply~locacc[Labelled(String.lowercasename),f])inletacc=match!any_recursivewith|false->[%exprfun(v:[%tsource_type]):[%ttarget_type]->[%erhs]]|true->[%exprletrec[%ppvar~locrecurse_name]=fun(v:[%tsource_type]):[%ttarget_type]->[%erhs]in[%eevar~locrecurse_name]]inletacc=Set.fold(Set.diffsource_variantstarget_variants)~init:acc~f:(funaccname->letname=remove_field_namenameinmk_pexp_fun~loc~nameacc)inletacc=Set.fold_rightmodified_variants~init:acc~f:(funnameacc->letname=modify_field_namenameinmk_pexp_fun~loc~nameacc)inacc;;moduleChanges_by_type=structtype'at={add:'a;modify:'a;set:'a;remove:'a}typekind=|Add|Modify|Set|Removeletsettkindvalue=matchkindwith|Add->{twithadd=value}|Modify->{twithmodify=value}|Set->{twithset=value}|Remove->{twithremove=value};;letgettkind=matchkindwith|Add->t.add|Modify->t.modify|Set->t.set|Remove->t.remove;;letcreatex={add=x;modify=x;set=x;remove=x}letmapt~f={add=ft.add;modify=ft.modify;set=ft.set;remove=ft.remove};;letto_listt=[t.add;t.modify;t.set;t.remove]endletconversions_of_td~ppx_name~target_type~rec_flagchangestd=let({add;modify;set;remove}:_Changes_by_type.t)=changesinletloc=td.ptype_locinletadd=Set.of_list(moduleString)addinletmodify=Set.of_list(moduleString)modifyinletremove=Set.of_list(moduleString)removeinletset=Set.of_list(moduleString)setinInvariant.all_disjoints~loc~add~modify~remove~set;letcurrent_type=Ast_helper.Typ.constr~loc(Located.map_lidenttd.ptype_name)(List.map~f:fsttd.ptype_params)inletconversions=matchtarget_typewith|None->[]|Sometarget_type->letto_target_name=name_of_type_name~dir:`To~source:td.ptype_name.txt~type_name:target_typeinletof_target_name=name_of_type_name~dir:`Of~source:td.ptype_name.txt~type_name:target_typeinletto_target,of_target=matchtd.ptype_kindwith|Ptype_recordlds->letcurrent_fields=Set.of_list(moduleString)(List.maplds~f:(funld->ld.pld_name.txt))inInvariant.things_are_known~thing_name:"fields"~supposed_to_be:"removed"~loc~all:current_fieldsremove;Invariant.things_are_known~thing_name:"fields"~supposed_to_be:"modified"~loc~all:current_fieldsmodify;Invariant.things_are_known~thing_name:"fields"~supposed_to_be:"set"~loc~all:current_fieldsset;letother_fields=Set.diff(Set.unioncurrent_fieldsadd)removeinletto_target=convert_record~loc~fields:lds~source_fields:current_fields~target_fields:other_fields~modified_fields:modify~set_fields:set~target_type~source_type:current_type~rec_flag~type_name:td.ptype_name.txtinletof_target=convert_record~loc~fields:lds~source_fields:other_fields~target_fields:current_fields~modified_fields:modify~set_fields:set~target_type:current_type~source_type:target_type~rec_flag~type_name:td.ptype_name.txtinto_target,of_target|Ptype_variantcdl->letcurrent_variants=Set.of_list(moduleString)(List.mapcdl~f:(funcd->cd.pcd_name.txt))inInvariant.things_are_known~thing_name:"variants"~supposed_to_be:"removed"~loc~all:current_variantsremove;Invariant.things_are_known~thing_name:"variants"~supposed_to_be:"modified"~loc~all:current_variantsmodify;ifnot(Set.is_emptyset)thenLocation.raise_errorf~loc"[set] is for record only";letother_variants=Set.diff(Set.unioncurrent_variantsadd)removeinletto_target=convert_variant~loc~constructors:cdl~source_variants:current_variants~target_variants:other_variants~modified_variants:modify~target_type~source_type:current_type~rec_flag~type_name:td.ptype_name.txtinletof_target=convert_variant~loc~constructors:cdl~source_variants:other_variants~target_variants:current_variants~modified_variants:modify~target_type:current_type~source_type:target_type~rec_flag~type_name:td.ptype_name.txtinto_target,of_target|Ptype_open->Location.raise_errorf~loc"%s: open types not supported"ppx_name|Ptype_abstract->Location.raise_errorf~loc"%s: abstract types not supported"ppx_namein[[%strilet[%ppvar~locto_target_name]=[%eto_target]];[%strilet[%ppvar~locof_target_name]=[%eof_target]]]inletextra_struct=matchtd.ptype_kind,target_typewith|Ptype_variantcdl,_->generate_stable_variant_module~loc~cdl~td|Ptype_record_,None->Location.raise_errorf~loc"%s: missing target version"ppx_name|Ptype_record_,Some_|(Ptype_open|Ptype_abstract),_->[]inextra_struct@conversions;;letfields_or_constructors()=letopenAst_patterninletrec_fields_pat=elist(pexp_ident(lident__))inletconstrs_pat=elist(pexp_construct(lident__)none)inaltrec_fields_patconstrs_pat;;lettype_pattern=letopenAst_patterninletident=map'(pexp_ident__)~f:(funloc_lid->Some(Ast_builder.Default.ptyp_constr~loc(Located.mk~loclid)[]))inlettype_=map'(* make sure we get a type constructor. *)(pexp_extension(extension(string"stable")(ptyp(ptyp_constr__'__))))~f:(funloc_lidparams->Some(Ast_builder.Default.ptyp_constr~loclidparams))inaltidenttype_;;letstable_changes=letraise_invalid_change_argument~loc=Location.raise_errorf~loc"Invalid change argument. Expected add, modify, set, or remove."inAttribute.declare"stable.changes"Type_declarationAst_pattern.(pstr(pstr_eval(pexp_apply(estring(string""))__)nil^::nil))(funargs:_Changes_by_type.t->letinit=Changes_by_type.createNoneinList.foldargs~init~f:(funacc(label,expression)->letloc=expression.pexp_locinletname=matchlabelwith|Labelledname->name|Nolabel|Optional_->raise_invalid_change_argument~locinletkind:Changes_by_type.kind=matchnamewith|"add"->Add|"modify"->Modify|"set"->Set|"remove"->Remove|_->raise_invalid_change_argument~locinletvalue=Ast_pattern.parse(fields_or_constructors())locexpressionFn.idinmatchChanges_by_type.getacckindwith|None->Changes_by_type.setacckind(Somevalue)|Some_->Location.raise_errorf~loc"%s argument was passed twice"name)|>Changes_by_type.map~f:(Option.value~default:[]));;letmake_stable_changes_attribute~loc?(add=[])?(modify=[])?(set=[])?(remove=[])()=letopen(valAst_builder.makeloc)inletmkidentx=ifChar.is_lowercasex.[0]thenpexp_ident(Located.lidentx)elsepexp_construct(Located.lidentx)Noneinletident_listnames=elist(List.map~f:mkidentnames)inletchange_expression=pexp_apply[%expr""][Labelled"add",ident_listadd;Labelled"set",ident_listset;Labelled"modify",ident_listmodify;Labelled"remove",ident_listremove]inattribute~name:(Located.mk(Attribute.namestable_changes))~payload:(PStr[pstr_evalchange_expression[]]);;letargs=Deriving.Args.(letchanges=pack2(pexp_loc__(fields_or_constructors()))inempty+>arg"version"type_pattern+>arg"add"changes+>arg"modify"changes+>arg"set"changes+>arg"remove"changes);;(* That's actually useless, it's just here so ppxlib's driver doesn't complain *)letrewrite_type_ext=Extension.declare"stable"Extension.Context.expressionAst_pattern.(ptyp(ptyp_constr__'__))(fun~loc~path:___->[%expr`Do_not_use_percent_stable_outside_of_deriving_stable]);;let()=Driver.register_transformation"stable"~extensions:[rewrite_type_ext]letgenppx_name~loc~path:_(rec_flag,tds)target_typeaddmodifysetremove=matchtdswith|[td]->letchanges_from_args:_Changes_by_type.t={add;modify;set;remove}inletchanges=matchAttribute.getstable_changestdwith|Somechanges_from_attribute->(matchChanges_by_type.to_listchanges_from_args|>List.find_map~f:Fn.idwith|None->()|Some(loc,_)->Location.raise_errorf~loc"The changes (add, modify, set, or remove) passed to\n\
[@@@@deriving %s] are unnecessary. They are already\n\
specified by the [@@@@stable.changes] attribute."ppx_name);changes_from_attribute|None->Changes_by_type.mapchanges_from_args~f:(Option.value_map~f:snd~default:[])inconversions_of_td~rec_flag~ppx_name~target_typechangestd|_->Location.raise_errorf~loc"mutually recursive types are not supported by ppx_stable_type";;letstable_record=letname="stable_record"inletstr_type_decl=Deriving.Generator.makeargs(genname)inDeriving.addname~str_type_decl;;letstable_variant=letname="stable_variant"inletstr_type_decl=Deriving.Generator.makeargs(genname)~deps:[]inDeriving.addname~str_type_decl;;