123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438openBaseopenPpxlibopenAst_builder.Default(* The scope in which to find [Optional_syntax]. [From_module] means using
module.Optional_syntax.Optional_syntax *)typemodule_scope=|Use_optional_syntax|Use_optional_syntax_optional_syntax|From_moduleoflongidentlocmoduleMatched_expression_element=structtypet={module_:module_scope;exp:expression}endtypet={default_module:module_scope;original_matched_expr:expression;elements:Matched_expression_element.tlist;match_loc:Location.t;cases:caselist}letmodule_scope_of_option=function|None->Use_optional_syntax|Somemodule_->From_modulemodule_;;letinfer_module_from_core_type~module_(core_type:core_type)=letdefault=module_scope_of_optionmodule_inmatchcore_type.ptyp_descwith|Ptyp_constr(longident,_params)->(matchlongident.txtwith|Lident_->Use_optional_syntax_optional_syntax|Ldot(longident,_label)->From_module{txt=longident;loc=core_type.ptyp_loc}|Lapply_->default)|_->default;;letexpand_matched_expr~(module_:longidentlocoption)matched_expr=letindividual_exprs=matchmatched_expr.pexp_descwith|Pexp_tupleexprs->exprs|_->[matched_expr]inList.mapindividual_exprs~f:(funexp->matchexp.pexp_descwith|Pexp_constraint(_exp,core_type)->{Matched_expression_element.module_=infer_module_from_core_type~module_core_type;exp}|_->{module_=module_scope_of_optionmodule_;exp});;letoptional_syntax_str="Optional_syntax"letoptional_syntax~module_:Longident.t=match(module_:module_scope)with|Use_optional_syntax->Lidentoptional_syntax_str|Use_optional_syntax_optional_syntax->Ldot(Lidentoptional_syntax_str,optional_syntax_str)|From_moduleid->Ldot(Ldot(id.txt,optional_syntax_str),optional_syntax_str);;leteoperator~loc~module_func=letlid:Longident.t=Ldot(optional_syntax~module_,func)inpexp_ident~loc(Located.mk~loclid);;leteunsafe_value=eoperator"unsafe_value"leteis_none=eoperator"is_none"letrecassert_binderpat=matchpat.ppat_descwith|Ppat_alias(pat,_)|Ppat_constraint(pat,_)->(* Allow "Some (_ as x)" and "Some (_ : typ)" *)assert_binderpat|Ppat_var_|Ppat_any->()|_->Location.raise_errorf~loc:pat.ppat_loc"sub patterns are restricted to variable names, wildcards and aliases";;letdisable_all_warningse=letattr=letloc=Location.noneinattribute~loc~name:{Location.loc;txt="ocaml.warning"}~payload:(PStr[pstr_eval~loc(estring~loc"-a")[]])in{ewithpexp_attributes=attr::e.pexp_attributes};;letvarnamei=Printf.sprintf"__ppx_optional_e_%i"iletevar~loci=evar~loc(varnamei)letpvar~loci=pvar~loc(varnamei)letget_pattern_and_bindings~module_ipattern=letreclooppatbindings=letloc=pat.ppat_locinletoption_bindingx=value_binding~loc~pat:(ppat_var~locx)~expr:(evar~loci)inletunsafe_value_bindingx=value_binding~loc~pat:[%pat?([%px]:_)]~expr:(eapply~loc(eunsafe_value~loc~module_)[evar~loci])inmatchpatwith|{ppat_desc=Ppat_alias(pat,x);_}->letbinding=option_bindingxinlooppat(binding::bindings)|{ppat_desc=Ppat_varx;_}->letbinding=option_bindingxin[%pat?_],binding::bindings|[%pat?Some[%p?x]]->assert_binderx;letbinding=unsafe_value_bindingxin[%pat?false],binding::bindings|[%pat?None]->[%pat?true],bindings|[%pat?_]->pat,bindings|_->Location.raise_errorf~loc:pat.ppat_loc"only variable names, None, Some, _ and aliases are supported in [%%optional ]"inlet{ppat_desc;_},bindings=looppattern[]in(* by only using the ppat_desc from the pattern we just generated we ensure that the
location of the original pattern is kept. *){patternwithppat_desc},bindings;;letrewrite_case~match_loc~modules~default_module{pc_lhs=pat;pc_rhs=body;pc_guard}=letmodules_array=Array.of_listmodulesinletget_modulei=(* Sadly, we need to be able to handle the case when the length of the matched
expression doesn't equal the length of the case, in order to produce useful
error messages (with the proper types). *)ifi<Array.lengthmodules_arraythenmodules_array.(i)elsedefault_moduleinletppat_desc,bindings=matchpat.ppat_descwith|(Ppat_alias(_,x)|Ppat_varx)whenArray.lengthmodules_array>1->Location.raise_errorf~loc:pat.ppat_loc"this pattern would bind a tuple to the variable %s, which is unsupported in \
[%%optional ]"x.txt|Ppat_tuplepatts->letpatts,bindings=List.mapipatts~f:(funipatt->letmodule_=get_moduleiinget_pattern_and_bindings~module_ipatt)|>List.unzipinPpat_tuplepatts,List.concatbindings|_->letpat,bindings=get_pattern_and_bindings0pat~module_:(List.hd_exnmodules)inpat.ppat_desc,bindingsinletpc_lhs={patwithppat_desc}inletpc_rhs,pc_guard=matchbindingswith|[]->body,pc_guard|_->(pexp_let~loc:match_locNonrecursivebindingsbody,Option.mappc_guard~f:(funpc_guard->pexp_let~loc:pc_guard.pexp_locNonrecursivebindingspc_guard))in{pc_lhs;pc_rhs;pc_guard};;(** Take the matched expression and replace all its components by a variable, which will
have been bound previously, wrapped by [wrapper].
We do keep the location of the initial component for the new one. *)letrewrite_matched_exprt~wrapper=letsubst_and_wrapi{Matched_expression_element.module_;exp}=letloc={exp.pexp_locwithloc_ghost=true}inwrapper~module_i(evar~loci)inletpexp_desc=matcht.elementswith|[singleton]->(subst_and_wrap0singleton).pexp_desc|list->Pexp_tuple(List.mapilist~f:subst_and_wrap)inletpexp_loc={t.original_matched_expr.pexp_locwithloc_ghost=true}in{t.original_matched_exprwithpexp_desc;pexp_loc};;letreal_matcht=letnew_matched_expr=rewrite_matched_exprt~wrapper:(fun~module_(_:int)expr->letloc=expr.pexp_locineapply~loc(eis_none~loc~module_)[expr])inletmodules=List.mapt.elements~f:(fun{module_;_}->module_)inletcases=List.mapt.cases~f:(rewrite_case~match_loc:t.match_loc~modules~default_module:t.default_module)in(* we can disable the warning here as we rely on the other match we generate for
error messages. *)disable_all_warnings(pexp_match~loc:t.match_locnew_matched_exprcases);;(* Split a [Some _ as x] pattern into two, [Some _] and [_ as x]. The latter could just be
[x], but we err on the side of caution and replace a [Ppat_alias _] with another
[Ppat_alias _] rather than a [Ppat_var _]. Because this is taking the [Ppat_constr _]
pattern that was nested inside the alias and turning it into a sibling AST node, they
should be ghosts. However, we take some care to provide reasonable locations for the
new AST nodes, such that the left-hand pattern extends from the start of the outermost
alias to the end of the innermost pattern, and the right-hand pattern extends from the
start of the alias binding (i.e. [x]) to the end of the outermost alias. *)letsplit_fake_alias_patternlhs((x,_)asalias)aliases=(* [ppat_loc_stack] represents relocations of a pattern by the parser, in particular due
to nested parentheses. [ppat_loc] is the outermost span, and the stack is ordered
from outer to inner. [ppat_loc] and [ppat_loc_stack] can thus be viewed as a
nonempty stack. Here we take a list of locations ordered from inner to outer (i.e.
reversed) and push them down onto an existing nonempty stack. [merge] is used to
enforce constraints on the boundaries of the left and right patterns (see above). *)letrelocate_from_inner_to_outerlocslocloc_stack~merge=List.foldlocs~init:(loc,loc_stack)~f:(fun((hd,tl)asacc)loc->letloc={loc_start=mergeloc.loc_starthd.loc_start;loc_end=mergeloc.loc_endhd.loc_end;loc_ghost=false(* Needed for unused-var check to work on alias patterns. *)}inifLocation.comparelochd=0thenaccelseloc,hd::tl)inList.fold(alias::aliases)~init:(lhs,ppat_any~loc:{x.locwithloc_ghost=true})~f:(fun(lhs,rhs)(x,pat)->letinner_loc,rev_loc_stack=List.foldpat.ppat_loc_stack~init:(pat.ppat_loc,[])~f:(fun(hd,tl)loc->loc,hd::tl)inletlhs_loc,lhs_loc_stack=relocate_from_inner_to_outer(inner_loc::rev_loc_stack)lhs.ppat_loclhs.ppat_loc_stack~merge:Location.min_posinletrhs_loc,rhs_loc_stack=relocate_from_inner_to_outerrev_loc_stack{inner_locwithloc_start=rhs.ppat_loc.loc_start}[]~merge:Location.max_posin({lhswithppat_loc=lhs_loc;ppat_loc_stack=lhs_loc_stack},{ppat_desc=Ppat_alias(rhs,x);ppat_loc=rhs_loc;ppat_loc_stack=rhs_loc_stack;ppat_attributes=pat.ppat_attributes}));;(* A "fake" pattern is one which requires a [Foo.t option] expression to be typed
correctly, effectively only [Ppat_construct _], but we rely on
[get_pattern_and_bindings] to complain about other unsupported patterns. A "real"
pattern is one which requires a [Foo.Option.t] expression. Patterns requiring "both"
must be matched against a [Foo.t option * Foo.Option.t]. Wildcards can be used with
"any" of the above. We could always generate "both" patterns but it would come at the
cost of potentially confusing type errors, so we avoid them where possible. *)letanalyze_fake_patternpattern=matchpattern.ppat_descwith|Ppat_any->`Any|Ppat_var_->`Real|Ppat_alias(pat,x)->letreclooppatternaliasaliases=matchpattern.ppat_descwith|Ppat_any|Ppat_var_->`Real|Ppat_alias(pat,x)->looppat(x,pattern)(alias::aliases)|_->`Both(split_fake_alias_patternpatternaliasaliases)inlooppat(x,pattern)[]|_->`Fake;;letmake_fake_pattern_compatibleexpr_kindpatt_kindpattern=matchexpr_kind,patt_kindwith|`Fake,`Fake|`Real,`Real|_,`Any->pattern|`Both,((`Fake|`Real|`Both_)askind)->letwildcard=lazy(ppat_any~loc:Location.none)inletfake,real=matchkindwith|`Fake->pattern,forcewildcard|`Real->forcewildcard,pattern|`Bothboth->bothinppat_tuple~loc:{pattern.ppat_locwithloc_ghost=true}[fake;real]|`Any,(`Fake|`Real|`Both_)|`Fake,(`Real|`Both_)|`Real,(`Fake|`Both_)->Location.raise_errorf~loc:pattern.ppat_loc"Bug in [%%optional ]: this pattern is incompatible with the corresponding fake \
expression";;lettranslate_fake_match_casescases~num_exprs=letpatt_kinds=Array.of_list_mapcases~f:(fun{pc_lhs=pat;_}->matchpat.ppat_descwith|Ppat_tuplepatts->Array.of_list_map~f:analyze_fake_patternpatts|_->[|analyze_fake_patternpat|])inletmax_num_kinds=Array.foldpatt_kinds~init:num_exprs~f:(funnum_kindspatt_kinds->maxnum_kinds(Array.lengthpatt_kinds))inletexpr_kinds=Array.create~len:max_num_kinds`Anyin(* We need to ensure the fake expression can generate bindings for all of its
corresponding patterns. *)Array.iterpatt_kinds~f:(funpatt_kinds->Array.iteripatt_kinds~f:(funepatt_kind->matchexpr_kinds.(e),patt_kindwith|`Both,_|`Fake,`Fake|`Real,`Real|_,`Any->()|`Any,((`Fake|`Real)askind)->expr_kinds.(e)<-kind|`Fake,`Real|`Real,`Fake|_,`Both_->expr_kinds.(e)<-`Both));letcases=List.mapicases~f:(func({pc_lhs=pat;_}ascase)->letpatt_kinds=patt_kinds.(c)inletmake_fake_pattern_compatibleepat=make_fake_pattern_compatibleexpr_kinds.(e)patt_kinds.(e)patinletpat=matchpat.ppat_descwith|Ppat_tuplepatts->letpatts=List.mapi~f:make_fake_pattern_compatiblepattsin{patwithppat_desc=Ppat_tuplepatts}|_->make_fake_pattern_compatible0patin{casewithpc_lhs=pat})incases,expr_kinds;;(* The fake match's aliases have types like [int option] instead of [Int.Option], so we
match on tuples e.g. [Some (unsafe_value x), x], then rewrite patterns to attach
aliases and vars to the second element rather than the first. *)letfake_matcht=letcases,kinds=translate_fake_match_casest.cases~num_exprs:(List.lengtht.elements)inletnew_matched_expr=rewrite_matched_exprt~wrapper:(fun~module_iexpr->letloc=expr.pexp_locinletfake_option=[%expr(* This code will never be executed, it is just here so the type checker
generates nice error messages. *)if[%eeis_none~loc~module_][%eexpr]thenNoneelseSome([%eeunsafe_value~loc~module_][%eexpr])]inmatchkinds.(i)with|`Fake->fake_option|`Any|`Real->expr|`Both->[%expr[%efake_option],[%eexpr]])inpexp_match~loc:{t.match_locwithloc_ghost=true}new_matched_exprcases;;letbindings_for_matched_exprmatched_expr=letbindiexpr=letloc={expr.pexp_locwithloc_ghost=true}invalue_binding~loc~pat:(pvar~loci)~exprinList.mapimatched_expr~f:(funi{Matched_expression_element.exp;_}->bindiexp);;letexpand_match~match_loc~(module_:longidentlocoption)matched_exprcases=lett={default_module=module_scope_of_optionmodule_;original_matched_expr=matched_expr;elements=expand_matched_expr~module_matched_expr;match_loc;cases}inletfake_match=(* The types in this branch actually match what the user would expect given the source
code, so we tell merlin to do all its work in here. *)Merlin_helpers.focus_expression(fake_matcht)inletreal_match=(* The types here actually have nothing to do with what's in the source ([bool]
appears for example), so we tell merlin to avoid that branch. *)Merlin_helpers.hide_expression(real_matcht)inletbindings=bindings_for_matched_exprt.elementsinletloc={match_locwithloc_ghost=true}inpexp_let~locNonrecursivebindings(pexp_ifthenelse~loc(ebool~locfalse)fake_match(Somereal_match));;(* We add the indirection instead of directly matching on [pexp_match] when declaring the
extension because we want more informative error messages than "Extension was not
translated". *)letexpand_match~loc~path:_~arg:(module_:longidentlocoption)e=Ast_pattern.parseAst_pattern.(pexp_match____)loce~on_error:(fun()->Location.raise_errorf~loc"[%%optional ] must apply to a match statement")(expand_match~match_loc:e.pexp_loc~module_);;letoptional=Extension.declare_with_path_arg"optional"Extension.Context.expressionAst_pattern.(single_expr_payload__)expand_match;;let()=Driver.register_transformation"optional"~extensions:[optional]