Legend:
Page
Library
Module
Module type
Parameter
Class
Class type
Source
Source file type_deriver.ml
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401openPpxlibopenAst_builder.DefaultopenStdLabelsopenUtils(******************************************************************************)(** {1 Generator for Auxiliary Datatypes} *)(** Walks over all the [val ...] declarations in a module signature
and creates the corresponding definition of the [expr] ADT
- The return type is a list of pairs, where each pair
consists of the declaration for the [expr] constructor,
along with the return type of the function (expressed as
a [core_type]) *)letmk_expr_cstrs(sig_items:signature):(constructor_declaration*core_type)list=letabs_ty_names=get_abs_ty_namessig_itemsinList.fold_leftsig_items~init:[]~f:(funacc{psig_desc;_}->matchpsig_descwith|Psig_type(_,_)->[]|Psig_value{pval_name;pval_type;pval_loc;_}->letname:string=String.capitalize_asciipval_name.txtin(* Exclude the return type of the function from the list of arg types
for the [expr] constructor *)letarg_tys=remove_last(get_arg_tys_of_expr_cstrpval_typeabs_ty_names)in(* Return type of the function *)letret_ty=get_ret_typval_typein(mk_cstr~name~loc:pval_loc~arg_tys,ret_ty)::acc|Psig_attribute_->failwith"TODO: handle attribute [@@@id]"|Psig_extension(_,_)->failwith"TODO: handle extensions"|_->failwith"TODO: not sure how to handle other kinds of [signature_item_desc]")|>List.rev(** Extracts the unique return types of all [val] declarations within a
module signature *)letuniq_ret_tys(sig_items:signature):core_typelist=List.fold_leftsig_items~init:[]~f:(funacc{psig_desc;_}->matchpsig_descwith|Psig_value{pval_type;_}->letty=get_ret_typval_typeinifList.memty~set:accthenaccelsety::acc|_->acc)|>List.rev(** Helper function for creating the constructors of the [ty] and [value]
algebraic data types
- The argument [sig_items] contains the contents of a module signature
- [~f] is a function that specifies how to turn a [core_type] into a
[constructor_declaration] *)letmk_cstr_aux(sig_items:signature)~(f:core_type->constructor_declaration):constructor_declarationlist=letret_tys=uniq_ret_tyssig_itemsinletuniq_ret_tys=List.sort_uniqret_tys~cmp:(funt1t2->String.compare(string_of_monomorphized_tyt1)(string_of_monomorphized_tyt2))inList.mapuniq_ret_tys~f(** Constructs the definition of the [ty] algebraic data type
based on the unique return types of all [val] declarations within
the module signature *)letmk_ty_cstrs(sig_items:signature):constructor_declarationlist=mk_cstr_auxsig_items~f:(funty->mk_cstr~name:(string_of_monomorphized_tyty)~loc:ty.ptyp_loc~arg_tys:[])(** [mk_val_cstr ty] constructors the corresponding constructor declaration
for the [value] datatype, given some [core_type] [ty]
- e.g. if [ty = Int], [mk_val_cstr] returns the declaration for
the [ValInt] constructor *)letmk_val_cstr(ty:core_type):constructor_declaration=mk_cstr~name:("Val"^string_of_monomorphized_tyty)~loc:ty.ptyp_loc~arg_tys:[ty](** Constructs the definition of the [value] algebraic data type
based on the inhabitants of the [ty] ADT *)letmk_val_cstrs(sig_items:signature):constructor_declarationlist=mk_cstr_auxsig_items~f:mk_val_cstr(** Takes the name of a type and produces the name of its
corresponding QuickCheck generator *)letmk_generator_name(s:string):string=Printf.sprintf"quickcheck_generator_%s"s(** Produces an atomic QuickCheck generator for the given [core_type]
- [abs_tys] is an association list consisting of type names & type parameters
for the abstract types in the signature *)letrecgen_atom~(loc:Location.t)(ty:core_type)~(abs_tys:(string*core_typelist)list):expression=matchty.ptyp_descwith|Ptyp_constr(ty_name,[])->((* Check whether the type is an abstract type in the signature *)letty_str=(* Convert [expr] to [t] by default (TODO: may want to find a better
representation in the future) *)ifString.equal(string_of_lidentty_name.txt)"expr"then"t"elsestring_of_lidentty_name.txtinmatchList.assoc_optty_strabs_tyswith|Some[]->(* If the abstract type has no type params, produce a recursive call with
[T] *)letty_expr=evar~loc"T"in[%exprwith_size~size:(k/2)(gen_expr[%ety_expr])]|Sometyvars->(* Monomorphize all type variables, obtain their string representation &
concat the resultant string, so [('a, 'b) t] becomes [IntIntT] *)lettyvars_prefix=String.concat~sep:""(List.map~f:string_of_monomorphized_tytyvars)inletty_expr=evar~loc(tyvars_prefix^"T")in[%exprwith_size~size:(k/2)(gen_expr[%ety_expr])]|None->(* Assume [quickcheck_generator_ty] exists for any other non-parameterized
type [ty] *)unapplied_type_constr_conv~locty_name~f:mk_generator_name)(* For parameterized types, recursively derive generators for their type
parameters *)|Ptyp_constr(ty_name,ty_params)->letargs=List.map~f:(gen_atom~loc~abs_tys)ty_paramsintype_constr_conv~locty_name~f:mk_generator_nameargs|Ptyp_any->mk_error_expr~loc:ty.ptyp_loc"types must be instantiated in order to derive a QuickCheck generator"|Ptyp_var_->(* Instantiate type variables with [int] *)[%exprquickcheck_generator_int]|Ptyp_tupletys->(* [Core.Quickcheck.Generator] only supports tuples of length 2 - 6 *)letn=List.lengthtysinifn>=2&&n<=6thenlettuple_gen=evar~loc@@Printf.sprintf"tuple%d"ninletargs=List.map~f:(gen_atom~loc~abs_tys)tysineapply~loctuple_genargselsepexp_extension~loc@@Location.error_extensionf~loc"Unable to derive generator for product type %s with %d types"(Ppxlib.string_of_core_typety)n(* TODO: derive QC generator for unary functions of type [int -> int] *)|Ptyp_arrow(Nolabel,_,_)->pexp_extension~loc@@Location.error_extensionf~loc"Function types not supported yet (to be implemented)"|Ptyp_arrow(Labelledlbl,_,_)|Ptyp_arrow(Optionallbl,_,_)->pexp_extension~loc@@Location.error_extensionf~loc"Unable to derive QuickCheck generator for function type %s with \
labelled/optional argument %s"(Ppxlib.string_of_core_typety)lbl|_->pexp_extension~loc@@Location.error_extensionf~loc"Unable to derive QuickCheck generator for type %s"(Ppxlib.string_of_core_typety)(** Produces the name of QuickCheck generators corresponding to a list of
[constructor_declaration]s (by prepending the prefix "gen" to each
constructor's name) *)letmint_generator_names(cstrs:constructor_declarationlist):stringlist=List.map~f:(funcstr->letcstr_name=cstr.pcd_name.txtinExpansion_helpers.mangle(Prefix"gen")(uncapitalizecstr_name))cstrs(** Helper function for producing the RHS of the pattern match in gen_expr
- [abs_tys] is an association list consisting of type names & type parameters
for the abstract types in the signature *)letgen_expr_rhs~(loc:Location.t)(cstrs:constructor_declarationlist)~(abs_tys:(string*core_typelist)list):expression=matchcstrswith|[]->failwith"impossible"|_->letgenerator_names=mint_generator_namescstrsinList.map2cstrsgenerator_names~f:(funcstrcstr_gen_name->letcstr_name_evar=evar~loccstr.pcd_name.txtinletcstr_arg_tys=get_cstr_arg_tyscstrin(* The name of the generator for the symbolic expression, e.g.
[gen_Empty] *)letcstr_gen_name_var=pvar~loccstr_gen_namein(* Generators for nullary constructors are trivial, i.e. they just
[return] the constructor *)ifget_cstr_aritycstr=0thenvalue_binding~loc~pat:cstr_gen_name_var~expr:[%exprreturn[%ecstr_name_evar]]else(* Fresh names for the generators of the constructor arguments *)letarg_gen_names:stringlist=List.map~f:(fun_->gen_symbol~prefix:"g"())cstr_arg_tysin(* [let g1 = gen_int and g2 = gen_string in ...] *)letatomic_generators:value_bindinglist=List.map2~f:(funtyarg_gen->letpat=pvar~locarg_geninletgen_body=gen_atom~locty~abs_tysinvalue_binding~loc~pat~expr:gen_body)cstr_arg_tysarg_gen_namesinletgen_cstr_let_body=matcharg_gen_nameswith|[]->failwith"impossible, arity must be > 0"|[g]->(* Generate a fresh name for the random [expr] *)letvar=gen_symbol~prefix:"e"()in(* Then, apply the constructor to the random [expr] *)[%expr[%eevar~locg]>>|fun[%ppvar~locvar]->[%ecstr_name_evar][%eevar~locvar]]|gs->letn=List.lengthgsinifn>=2&&n<=6thenlettuple_gen=evar~loc@@Printf.sprintf"tuple%d"ninletgenerators=List.map~f:(evar~loc)gsinletvars=List.map~f:(fun_->gen_symbol~prefix:"e"())gsinletargs_pat=ppat_tuple~loc(List.map~f:(pvar~loc)vars)inletargs_expr=pexp_tuple~loc(List.map~f:(evar~loc)vars)in[%expr[%eeapply~loctuple_gengenerators]>>|fun[%pargs_pat]->[%ecstr_name_evar][%eargs_expr]]elsepexp_extension~loc@@Location.error_extensionf~loc"Functions with arity %d not supported, max arity is 6\n"nin(* [ let g1 = ... and g2 = ... in ... ] *)letgen_cstr_let_expr=pexp_let~locNonrecursiveatomic_generatorsgen_cstr_let_bodyin(* [let gen_is_empty = ... ] *)value_binding~loc~pat:cstr_gen_name_var~expr:gen_cstr_let_expr)|>funval_bindings->letgen_name_evars=elist~loc(List.map~f:(evar~loc)generator_names)inpexp_let~locNonrecursiveval_bindings[%exprunion[%egen_name_evars]](** Determines if an [expr] constructor can be used as a base case for
[gen_expr]. A constructor can be used as the base case if:
- It is nullary
- It has no arguments of type [expr] (i.e. the corresponding function
in the signature has no arguments of type [t])
- Note: constructors with record arguments are currently unsupported. *)letis_base_case(cstr:constructor_declaration):bool=letloc=cstr.pcd_locinmatchcstr.pcd_argswith|Pcstr_tuple[]->true|Pcstr_tuplexs->(* Check whether there are any arguments of type [expr] *)list_is_empty(List.filter~f:(equal_core_type[%type:expr])xs)|Pcstr_record_->false(** [check_type_is_concrete abs_ty_names ty] determines whether [ty] is a
{i concrete type} based on [abs_ty_names], a list containing the names of
abstract types defined in a signature.
For example, if a module signature defines an abstract type ['a t],
then [int t] would {i not} be concrete, but [int] and [bool] would be
considered concrete.
- Note: type variables (e.g. ['a]) are considered concrete by this function
(since they're technically not defined inside a module signature) *)letreccheck_type_is_concrete(abs_ty_names:stringlist)(ty:core_type):bool=matchty.ptyp_descwith(* For arrow & product types, check if all constituent types are concrete via
structural recursion *)|Ptyp_arrow(_,t1,t2)->check_type_is_concreteabs_ty_namest1&&check_type_is_concreteabs_ty_namest2|Ptyp_tupletys->List.fold_left~f:(funacct->acc&&check_type_is_concreteabs_ty_namest)~init:truetys(* For base types (nullary type constructors), check if the name of the type
constructor is contained in the list [abs_ty_names] *)|Ptyp_constr({txt=tyconstr;_},[])->lettyconstr_name=string_of_lidenttyconstrinnot(List.memtyconstr_name~set:abs_ty_names)(* Do the same for parametreized types, but in addition, also check that all
the type parameters are concrete *)|Ptyp_constr({txt=tyconstr;_},arg_tys)->lettyconstr_name=string_of_lidenttyconstrin(not(List.memtyconstr_name~set:abs_ty_names))&&List.fold_left~f:(funaccarg_ty->acc&&check_type_is_concreteabs_ty_namesarg_ty)~init:truearg_tys|_->true(** [mk_gen_expr_case abs_tys ~is_base_case ty rhs_cstrs] constructs a single case in the
pattern-match of the body of [gen_expr].
- [abs_tys] is a list containing pairs of the form
[(<type_name>, <list_of_type_parameters>)]. Most likely, this list is
obtained by calling [get_ty_decls_from_sig] in [getters.ml].
- [ty] is the type we are matching on in the LHS of the pattern match
inside [gen_expr]
- [rhs_cstrs] are the constructors for [expr] that have that type (to be
generated on the RHS of the pattern match).
- [is_base_case] is an optional Boolean argument that indicates whether
the constructors in [rhs_cstrs] are base cases for [gen_expr]
(as determined by the [is_base_case] function). This parameter
defaults to [false]. *)letmk_gen_expr_case(abs_tys:(string*core_typelist)list)?(is_base_case=false)(ty:core_type)(rhs_cstrs:constructor_declarationlist):case=letloc=ty.ptyp_locin(* When matching on base cases, we want [gen_expr]'s QuickCheck size parameter
to be 0. For non-trivial cases, any non-zero size is fine. *)letsize_pat=ifis_base_casethen[%pat?0]else[%pat?_]in(* The LHS of the pattern match is a pair fo the form [(ty,
quickcheck_size]) *)letlhs=ppat_tuple~loc[pvar~loc(string_of_monomorphized_tyty);size_pat]inletrhs_exprs=gen_expr_rhs~loc:lhs.ppat_loc~abs_tysrhs_cstrsinletrhs=[%expr[%erhs_exprs]]incase~lhs~guard:None~rhs(** Creates the main case statement in [gen_expr] *)letgen_expr_cases(sig_items:signature):caselist=letopenBase.List.Associnletabs_tys=get_ty_decls_from_sigsig_itemsinletabs_ty_names=List.map~f:fstabs_tysin(* Maps [ty]s to [expr]s *)letskeleton:(core_type*constructor_declarationlist)list=inverse(mk_expr_cstrssig_items)|>sort_and_group~compare:compare_core_typeinList.concat_mapskeleton~f:(fun(ty,rhs_cstrs)->ifnot(check_type_is_concreteabs_ty_namesty)thenletbase_case_cstrs,non_trivial_cstrs=List.partition~f:is_base_caserhs_cstrsinletbase_case=mk_gen_expr_caseabs_tystybase_case_cstrs~is_base_case:trueinletnon_trivial_cases=[mk_gen_expr_caseabs_tystynon_trivial_cstrs~is_base_case:false]inbase_case::non_trivial_caseselse[mk_gen_expr_caseabs_tystyrhs_cstrs~is_base_case:false])(** Derives the [gen_expr] QuickCheck generator *)letderive_gen_expr~(loc:Location.t)(sig_items:signature):expression=(* Derive [let open] expressions for the necessary modules *)letcore_mod=module_expr_of_string~loc"Core"inletqc_gen_mod=module_expr_of_string~loc"Quickcheck.Generator"inletlet_syntax_mod=module_expr_of_string~loc"Let_syntax"inletmatch_exp=pexp_match~loc[%exprty,k](gen_expr_casessig_items)inletbody=[%exprsize>>=funk->[%ematch_exp]]inlet_open~loccore_mod(let_open_twice~locqc_gen_modlet_syntax_modbody)(** Produces the attribute [[@@deriving show { with_path = false }]] *)letderiving_show~(loc:Location.t):attribute=deriving_attribute~loc[%exprshow{with_path=false}](** Walks over a module signature definition and extracts the
abstract type declaration, producing the definition
the [expr] and [ty] algebraic data types *)letgenerate_types_from_sig~(ctxt:Expansion_context.Deriver.t)(mt:module_type_declaration):structure_itemlist=letloc=Expansion_context.Deriver.derived_item_locctxtinmatchmtwith|{pmtd_type=Somemod_type;pmtd_loc;_}->(matchmod_typewith|{pmty_desc=Pmty_signaturesig_items;_}->(matchsig_itemswith|[]->[mk_error_pstr~local:pmtd_loc~global:loc"Module sig can't be empty"]|_->(* Type declarations for the [expr] & [ty] ADTs *)letexpr_td=mk_adt~loc~name:"expr"~cstrs:(List.map~f:fst(mk_expr_cstrssig_items))in(* Attach [[@@deriving show]] to the type definition *)letannotated_expr_td={expr_tdwithptype_attributes=[deriving_show~loc]}inletty_cstrs=mk_ty_cstrssig_itemsinletty_td=mk_adt~loc~name:"ty"~cstrs:ty_cstrsinletannotated_ty_td={ty_tdwithptype_attributes=[deriving_show~loc]}in[%str[%%ipstr_type~locRecursive[annotated_expr_td]][%%ipstr_type~locRecursive[annotated_ty_td]]letrecgen_exprty=[%ederive_gen_expr~locsig_items]])|_->failwith"TODO: other case for mod_type")|{pmtd_type=None;pmtd_loc;_}->[mk_error_pstr~local:pmtd_loc~global:loc"Can't derive for expressions that aren't module type declarations"](** Helper function: given [mod_ty], a module signature,
[get_expr_cstrs] produces [expr] constructor names & arguments
that match the declarations in the module signature *)letget_expr_cstrs(mod_ty:module_type):(Longident.tLocation.loc*patternoption*inv_ctx*core_type)list=matchmod_ty.pmty_descwith|Pmty_signaturesig_items->get_cstr_metadata(mk_expr_cstrssig_items)|_->failwith"TODO: get_expr_cstrs"