123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611openBaseopenPpxlibopenAst_builder.DefaultmoduleAttrs=structletignore_label_declaration=Attribute.declare"hash.ignore"Attribute.Context.label_declarationAst_pattern.(pstrnil)();;letignore_core_type=Attribute.declare"hash.ignore"Attribute.Context.core_typeAst_pattern.(pstrnil)();;letno_hashing_label_declaration=Attribute.declare"hash.no_hashing"Attribute.Context.label_declarationAst_pattern.(pstrnil)();;endletstr_attributes=[Attribute.TAttrs.ignore_core_type;Attribute.TAttrs.ignore_label_declaration;Attribute.TAttrs.no_hashing_label_declaration];;letis_ignored_genattrst=List.existsattrs~f:(funattr->Option.is_some(Attribute.getattrt));;letcore_type_is_ignoredct=is_ignored_gen[Attrs.ignore_core_type;Ppx_compare_expander.Compare.Attrs.ignore_core_type]ct;;letshould_ignore_label_declarationld=letwarning="[@hash.no_hashing] is deprecated. Use [@hash.ignore]."inletis_ignored=is_ignored_gen[Attrs.ignore_label_declaration;Ppx_compare_expander.Compare.Attrs.ignore_label_declaration]ld(* Avoid confusing errors with [ { mutable field : (value[@ignore]) } ]
vs [ { mutable field : value [@ignore] } ] by treating them the same. *)||core_type_is_ignoredld.pld_typeinmatchAttribute.getAttrs.no_hashing_label_declarationldwith|None->(ifis_ignoredthen`ignoreelse`incorporate),None|Some()->`ignore,Some(attribute_of_warningld.pld_locwarning);;(* Generate code to compute hash values of type [t] in folding style, following the structure of
the type. Incorporate all structure when computing hash values, to maximise hash
quality. Don't attempt to detect/avoid cycles - just loop. *)lethash_state_t~loc=[%type:Ppx_hash_lib.Std.Hash.state]lethash_fold_type~locty=letloc={locwithloc_ghost=true}in[%type:[%thash_state_t~loc]->[%tty]->[%thash_state_t~loc]];;lethash_type~locty=letloc={locwithloc_ghost=true}in[%type:[%tty]->Ppx_hash_lib.Std.Hash.hash_value];;(* [expr] is an expression that doesn't use the [hsv] variable.
Currently it's there only for documentation value, but conceptually it can be thought
of as an abstract type *)typeexpr=expression(* Represents an expression that produces a hash value and uses the variable [hsv] in
a linear way (mixes it in exactly once).
You can think of it as a body of a function of type [Hash.state -> Hash.state] *)moduleHsv_expr:sigtypetvalidentity:loc:location->tvalinvoke_hash_fold_t:loc:location->hash_fold_t:expr->t:expr->tvalcompose:loc:location->t->t->tvalcompile_error:loc:location->string->t(** the [_unchecked] functions all break abstraction in some way *)valof_expression_unchecked:expr->t(** the returned [expression] uses the binding [hsv] bound by [pattern] *)valto_expression:loc:location->t->pattern*expression(* [case] is binding a variable that's not [hsv] and uses [hsv] on the rhs
exactly once *)typecasevalcompile_error_case:loc:location->string->casevalpexp_match:loc:location->expr->caselist->t(* [lhs] should not bind [hsv] *)valcase:lhs:pattern->guard:exproption->rhs:t->case(* [value_binding]s should not bind or use [hsv] *)valpexp_let:loc:location->rec_flag->value_bindinglist->t->tvalwith_attributes:f:(attributelist->attributelist)->t->tend=structtypet=expressiontypenonreccase=caseletinvoke_hash_fold_t~loc~hash_fold_t~t=eapply~lochash_fold_t[[%exprhsv];t]letidentity~loc=[%exprhsv]letcompose~locab=[%exprlethsv=[%ea]in[%eb]];;letto_expression~locx=[%pat?hsv],xletof_expression_uncheckedx=xletpexp_match=pexp_matchletcase=caseletpexp_let=pexp_letletwith_attributes~fx={xwithpexp_attributes=fx.pexp_attributes}letcompile_error~locs=pexp_extension~loc(Location.Error.to_extension(Location.Error.createf~loc"%s"s));;letcompile_error_case~locs=case~lhs:(ppat_any~loc)~guard:None~rhs:(compile_error~locs);;endlethash_fold_int~loci:Hsv_expr.t=Hsv_expr.invoke_hash_fold_t~loc~hash_fold_t:[%exprPpx_hash_lib.Std.Hash.fold_int]~t:(eint~loci);;letspecial_case_types_named_t=function|`hash_fold->false|`hash->true;;lethash_fold_tn=matchtnwith|"t"whenspecial_case_types_named_t`hash_fold->"hash_fold"|_->"hash_fold_"^tn;;lethash_tn=matchtnwith|"t"whenspecial_case_types_named_t`hash->"hash"|_->"hash_"^tn;;(** renames [x] avoiding collision with [type_name] *)letrigid_type_var~type_namex=letprefix="rigid_"inifString.equalxtype_name||String.is_prefixx~prefixthenprefix^x^"_of_type_"^type_nameelsex;;letmake_type_rigid~type_name=letmap=objectinheritAst_traverse.mapassupermethod!core_typety=letptyp_desc=let()=(* making sure [type_name] is the only free type variable *)matchty.ptyp_descwith|Ptyp_constr(name,_args)->(matchname.txtwith|Ldot_|Lapply_->()|Lidentname->ifnot(String.equalnametype_name)thenLocation.raise_errorf~loc:ty.ptyp_loc"ppx_hash: make_type_rigid: unexpected type %S. expected to only \
find %S"(string_of_core_typety)type_name;())|_->()inmatchty.ptyp_descwith|Ptyp_vars->Ptyp_constr(Located.lident~loc:ty.ptyp_loc(rigid_type_var~type_names),[])|desc->super#core_type_descdescin{tywithptyp_desc}endinmap#core_type;;(* The only names we assume to be in scope are [hash_fold_<TY>]
So we are sure [tp_name] (which start with an [_]) will not capture them. *)lettp_namen=Printf.sprintf"_hash_fold_%s"nletwith_tupleloc(value:expr)xs(f:(expr*core_type)list->Hsv_expr.t):Hsv_expr.t=letnames=List.mapi~f:(funit->Printf.sprintf"e%d"i,t)xsinletpattern=letl=List.map~f:(fun(n,_)->pvar~locn)namesinppat_tuple~loclinlete=f(List.map~f:(fun(n,t)->evar~locn,t)names)inletbinding=value_binding~loc~pat:pattern~expr:valueinHsv_expr.pexp_let~locNonrecursive[binding]e;;lethash_ignore~locvalue=Hsv_expr.pexp_let~locNonrecursive[value_binding~loc~pat:[%pat?_]~expr:value](Hsv_expr.identity~loc);;letghostify_located(t:'aloc):'aloc={twithloc={t.locwithloc_ghost=true}};;letrechash_appliedtyvalue=letloc={ty.ptyp_locwithloc_ghost=true}inmatchty.ptyp_descwith|Ptyp_constr(name,ta)->letargs=List.mapta~f:(hash_fold_of_ty_fun~type_constraint:false)inHsv_expr.invoke_hash_fold_t~loc~hash_fold_t:(type_constr_conv~locname~f:hash_fold_args)~t:value|_->assertfalseandhash_fold_of_tuple~loctysvalue=with_tuplelocvaluetys(funelems1->List.fold_rightelems1~init:(Hsv_expr.identity~loc)~f:(fun(v,t)(result:Hsv_expr.t)->Hsv_expr.compose~loc(hash_fold_of_tytv)result))andhash_variant~locrow_fieldsvalue=letmaprow=matchrow.prf_descwith|Rtag({txt=cnstr;_},true,_)|Rtag({txt=cnstr;_},_,[])->Hsv_expr.case~guard:None~lhs:(ppat_variant~loccnstrNone)~rhs:(hash_fold_int~loc(Ocaml_common.Btype.hash_variantcnstr))|Rtag({txt=cnstr;_},false,tp::_)->letv="_v"inletbody=Hsv_expr.compose~loc(hash_fold_int~loc(Ocaml_common.Btype.hash_variantcnstr))(hash_fold_of_tytp(evar~locv))inHsv_expr.case~guard:None~lhs:(ppat_variant~loccnstr(Some(pvar~locv)))~rhs:body|Rinherit({ptyp_desc=Ptyp_constr(id,_);_}asty)->(* Generated code from..
type 'a id = 'a [@@deriving hash]
type t = [ `a | [ `b ] id ] [@@deriving hash]
doesn't compile: Also see the "sadly" note in: ppx_compare_expander.ml *)letv="_v"inHsv_expr.case~guard:None~lhs:(ppat_alias~loc(ppat_type~loc(ghostify_locatedid))(Located.mk~locv))~rhs:(hash_appliedty(evar~locv))|Rinheritty->lets=string_of_core_typetyinHsv_expr.compile_error_case~loc(Printf.sprintf"ppx_hash: impossible variant case: %s"s)inHsv_expr.pexp_match~locvalue(List.map~f:maprow_fields)andbranch_of_sumhsv~loccd=matchcd.pcd_argswith|Pcstr_tuple[]->letpcnstr=pconstructcdNoneinHsv_expr.case~guard:None~lhs:pcnstr~rhs:hsv|Pcstr_tupletps->letids_ty=List.mapitps~f:(funity->Printf.sprintf"_a%d"i,ty)inletlpatt=List.mapids_ty~f:(fun(l,_ty)->pvar~locl)|>ppat_tuple~locandbody=List.fold_leftids_ty~init:(Hsv_expr.identity~loc)~f:(funexpr(l,ty)->Hsv_expr.compose~locexpr(hash_fold_of_tyty(evar~locl)))inHsv_expr.case~guard:None~lhs:(pconstructcd(Somelpatt))~rhs:(Hsv_expr.compose~lochsvbody)|Pcstr_recordlds->letarg="_ir"inletpat=pvar~locarginletv=evar~locarginletbody=hash_fold_of_record~locldsvinHsv_expr.case~guard:None~lhs:(pconstructcd(Somepat))~rhs:(Hsv_expr.compose~lochsvbody)andbranches_of_sum=function|[cd]->(* this is an optimization: we don't need to mix in the constructor tag if the type
only has one constructor *)letloc=cd.pcd_locin[branch_of_sum(Hsv_expr.identity~loc)~loccd]|cds->List.mapicds~f:(funicd->letloc=cd.pcd_locinlethsv=hash_fold_int~lociinbranch_of_sumhsv~loccd)andhash_sum~loccdsvalue=Hsv_expr.pexp_match~locvalue(branches_of_sumcds)andhash_fold_of_tytyvalue=letloc={ty.ptyp_locwithloc_ghost=true}inifcore_type_is_ignoredtythenhash_ignore~locvalueelse(matchty.ptyp_descwith|Ptyp_constr_->hash_appliedtyvalue|Ptyp_tupletys->hash_fold_of_tuple~loctysvalue|Ptyp_varname->Hsv_expr.invoke_hash_fold_t~loc~hash_fold_t:(evar~loc(tp_namename))~t:value|Ptyp_arrow_->Hsv_expr.compile_error~loc"ppx_hash: functions can not be hashed."|Ptyp_variant(row_fields,Closed,_)->hash_variant~locrow_fieldsvalue|_->lets=string_of_core_typetyinHsv_expr.compile_error~loc(Printf.sprintf"ppx_hash: unsupported type: %s"s))andhash_fold_of_ty_fun~type_constraintty=letloc={ty.ptyp_locwithloc_ghost=true}inletarg="arg"inletmaybe_constrained_arg=iftype_constraintthenppat_constraint~loc(pvar~locarg)tyelsepvar~locarginlethsv_pat,hsv_expr=Hsv_expr.to_expression~loc(hash_fold_of_tyty(evar~locarg))ineta_reduce_if_possible[%exprfun[%phsv_pat][%pmaybe_constrained_arg]->[%ehsv_expr]]andhash_fold_of_record~locldsvalue=letis_evar=function|{pexp_desc=Pexp_ident_;_}->true|_->falseinassert(is_evarvalue);List.fold_leftlds~init:(Hsv_expr.identity~loc)~f:(funhsvld->Hsv_expr.compose~lochsv(letloc=ld.pld_locinletlabel=Located.maplidentld.pld_nameinletshould_ignore,should_warn=should_ignore_label_declarationldinletfield_handling=matchld.pld_mutable,should_ignorewith|Mutable,`incorporate->`error"require [@hash.ignore] or [@compare.ignore] on mutable record field"|(Mutable|Immutable),`ignore->`ignore|Immutable,`incorporate->`incorporateinlethsv=matchfield_handlingwith|`errors->Hsv_expr.compile_error~loc(Printf.sprintf"ppx_hash: %s"s)|`incorporate->hash_fold_of_tyld.pld_type(pexp_field~locvaluelabel)|`ignore->Hsv_expr.identity~locinmatchshould_warnwith|None->hsv|Someattribute->Hsv_expr.with_attributes~f:(funattributes->attribute::attributes)hsv));;lethash_fold_of_abstract~loctype_namevalue=letstr=Printf.sprintf"hash called on the type %s, which is abstract in an implementation."type_nameinHsv_expr.of_expression_unchecked[%exprlet_=hsvinlet_=[%evalue]infailwith[%eestring~locstr]];;(** this does not change behavior (keeps the expression side-effect if any),
but it can make the compiler happy when the expression occurs on the rhs
of an [let rec] binding. *)leteta_expand~locf=[%exprletfunc=[%ef]infunx->funcx];;letrecognize_simple_typety=matchty.ptyp_descwith|Ptyp_constr(lident,[])->Somelident|_->None;;lethash_of_ty_fun~special_case_simple_types~type_constraintty=letloc={ty.ptyp_locwithloc_ghost=true}inletarg="arg"inletmaybe_constrained_arg=iftype_constraintthenppat_constraint~loc(pvar~locarg)tyelsepvar~locarginmatchrecognize_simple_typetywith|Somelidentwhenspecial_case_simple_types->unapplied_type_constr_conv~loclident~f:hash_|_->lethsv_pat,hsv_expr=Hsv_expr.to_expression~loc(hash_fold_of_tyty(evar~locarg))in[%exprfun[%pmaybe_constrained_arg]->Ppx_hash_lib.Std.Hash.get_hash_value(let[%phsv_pat]=Ppx_hash_lib.Std.Hash.create()in[%ehsv_expr])];;lethash_structure_item_of_tdtd=letloc=td.ptype_locinmatchtd.ptype_paramswith|_::_->[]|[]->[(letbnd=pvar~loc(hash_td.ptype_name.txt)inlettyp=combinator_type_of_type_declarationtd~f:hash_typeinletpat=ppat_constraint~locbndtypinletexpected_scope,expr=letis_simple_typety=matchrecognize_simple_typetywith|Some_->true|None->falseinmatchtd.ptype_kind,td.ptype_manifestwith|Ptype_abstract,Sometywhenis_simple_typety->(`uses_rhs,hash_of_ty_fun~special_case_simple_types:true~type_constraint:falsety)|_->(`uses_hash_fold_t_being_defined,hash_of_ty_fun~special_case_simple_types:false~type_constraint:false{ptyp_loc=loc;ptyp_loc_stack=[];ptyp_attributes=[];ptyp_desc=Ptyp_constr({loc;txt=Lidenttd.ptype_name.txt},[])})inexpected_scope,value_binding~loc~pat~expr:(eta_expand~locexpr))];;lethash_fold_structure_item_of_tdtd~rec_flag=letloc={td.ptype_locwithloc_ghost=true}inletarg="arg"inletbody=letv=evar~locarginmatchtd.ptype_kindwith|Ptype_variantcds->hash_sum~loccdsv|Ptype_recordlds->hash_fold_of_record~locldsv|Ptype_open->Hsv_expr.compile_error~loc"ppx_hash: open types are not supported"|Ptype_abstract->(matchtd.ptype_manifestwith|None->hash_fold_of_abstract~loctd.ptype_name.txtv|Somety->(matchty.ptyp_descwith|Ptyp_variant(_,Open,_)|Ptyp_variant(_,Closed,Some(_::_))->Hsv_expr.compile_error~loc:ty.ptyp_loc"ppx_hash: cannot hash open polymorphic variant types"|Ptyp_variant(row_fields,_,_)->hash_variant~locrow_fieldsv|_->hash_fold_of_tytyv))inletvars=List.maptd.ptype_params~f:(funp->get_type_param_namep)inletextra_names=List.mapvars~f:(funx->tp_namex.txt)inlethsv_pat,hsv_expr=Hsv_expr.to_expression~locbodyinletpatts=List.mapextra_names~f:(pvar~loc)@[hsv_pat;pvar~locarg]inletbnd=pvar~loc(hash_fold_td.ptype_name.txt)inletscheme=combinator_type_of_type_declarationtd~f:hash_fold_typeinletpat=ppat_constraint~locbnd(ptyp_poly~locvarsscheme)inletexpr=eta_reduce_if_possible_and_nonrec~rec_flag(eabstract~locpattshsv_expr)inletuse_rigid_variables=matchtd.ptype_kindwith|Ptype_variant_->true|_->falseinletexpr=ifuse_rigid_variablesthen(lettype_name=td.ptype_name.txtinList.fold_rightvars~f:(funs->pexp_newtype~loc{txt=rigid_type_var~type_names.txt;loc=s.loc})~init:(pexp_constraint~locexpr(make_type_rigid~type_namescheme)))elseexprinvalue_binding~loc~pat~expr;;letpstr_value~locrec_flagbindings=matchbindingswith|[]->[]|nonempty_bindings->(* [pstr_value] with zero bindings is invalid *)[pstr_value~locrec_flagnonempty_bindings];;letstr_type_decl~loc~path:_(rec_flag,tds)=lettds=List.maptds~f:name_type_params_in_tdinletrec_flag=(objectinherittype_is_recursiverec_flagtdsassupermethod!label_declarationld=matchfst(should_ignore_label_declarationld)with|`ignore->()|`incorporate->super#label_declarationldmethod!core_typety=ifcore_type_is_ignoredtythen()elsesuper#core_typetyend)#go()inlethash_fold_bindings=List.map~f:(hash_fold_structure_item_of_td~rec_flag)tdsinlethash_bindings=List.concat(List.map~f:hash_structure_item_of_tdtds)inmatchrec_flagwith|Recursive->(* if we wanted to maximize the scope hygiene here this would be, in this order:
- recursive group of [hash_fold]
- nonrecursive group of [hash] that are [`uses_hash_fold_t_being_defined]
- recursive group of [hash] that are [`uses_rhs]
but fighting the "unused rec flag" warning is just way too hard *)pstr_value~locRecursive(hash_fold_bindings@List.map~f:sndhash_bindings)|Nonrecursive->letrely_on_hash_fold_t,use_rhs=List.partition_maphash_bindings~f:(function|`uses_hash_fold_t_being_defined,binding->Firstbinding|`uses_rhs,binding->Secondbinding)inpstr_value~locNonrecursive(hash_fold_bindings@use_rhs)@pstr_value~locNonrecursiverely_on_hash_fold_t;;letmk_sig~loc:_~path:_(_rec_flag,tds)=List.concat(List.maptds~f:(funtd->letmonomorphic=List.is_emptytd.ptype_paramsinletdefinition~f_type~f_name=lettype_=combinator_type_of_type_declarationtd~f:f_typeinletname=lettn=td.ptype_name.txtinf_nametninletloc=td.ptype_locinpsig_value~loc(value_description~loc~name:{td.ptype_namewithtxt=name}~type_~prim:[])inList.concat[[definition~f_type:hash_fold_type~f_name:hash_fold_];(ifmonomorphicthen[definition~f_type:hash_type~f_name:hash_]else[])]));;letsig_type_decl~loc~path(rec_flag,tds)=matchmk_named_sig~loc~sg_name:"Ppx_hash_lib.Hashable.S"~handle_polymorphic_variant:truetdswith|Someinclude_info->[psig_include~locinclude_info]|None->mk_sig~loc~path(rec_flag,tds);;lethash_fold_core_typety=hash_fold_of_ty_fun~type_constraint:truetylethash_core_typety=hash_of_ty_fun~special_case_simple_types:true~type_constraint:truety;;