123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643openBaseopenPpxlibopenAst_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=matchPpxlib_jane.Jane_syntax.Expression.of_astmatched_exprwith|Some(Jexp_tuple_fields,_attrs)->Location.raise_errorf~loc:matched_expr.pexp_loc"labeled tuples are unsupported in [%%optional ]"|None|Some_->(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";;letchange_warningswarnings_stringe=letattr=letloc=Location.noneinattribute~loc~name:{Location.loc;txt="ocaml.warning"}~payload:(PStr[pstr_eval~loc(estring~locwarnings_string)[]])in{ewithpexp_attributes=attr::e.pexp_attributes};;letdisable_all_warningse=change_warnings"-a"elethide_expre=e|>disable_all_warnings|>Merlin_helpers.hide_expressionletdisable_unused_var_warninge=change_warnings"-unused-var"eletvarnamei=Printf.sprintf"__ppx_optional_e_%i"iletevar~loci=evar~loc(varnamei)letpvar~loci=pvar~loc(varnamei)letget_pattern_and_bindings~loc~module_ipattern=letreclooppatbindings=letoption_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|[%pat?[%p?l]|[%p?r]]->(* At this scope, we're considering a single optional value, so all option bindings
must be the same option, and all value bindings must be the same value. The fake
match will cause the compiler to complain if both sides of the pattern do not
bind the same values, or bind them with different types. So we can safely ignore
one set of bindings here, as we know the other set must be equivalent. *)letl,bindings=looplbindingsandr,(_:value_bindinglist)=looprbindingsin(* N.b. this could be [%pat? [%p l] | [%p r]] but it breaks ocamlformat. *){patwithppat_desc=Ppat_or(l,r)},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{patternwithppat_desc;ppat_loc=loc},bindings;;letignore_patternbinding={bindingwithpvb_pat=Merlin_helpers.hide_patternbinding.pvb_pat};;letrecrewrite_case~loc~modules_array~default_module~unboxed(* true <=> we're in a [%optional_u] *){pc_lhs=pat;pc_rhs=body;pc_guard}=letget_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_moduleinletsingle_pattern~ppat_desc~bindings=(* Merlin_helpers.hide_pattern: this overlaps with the pattern used
in the LHS of the bindings, but we don't want an error. Yet we also
don't want this to be a ghost location, because if the pattern is
ill-typed (e.g. too many patterns), we want the location to point
to the pattern. We hide this pattern, not the LHS of let-bindings, so
that merlin's go-to-definition works. *)letpc_lhs=Merlin_helpers.hide_pattern{patwithppat_desc}inletpc_rhs,pc_guard=matchbindingswith|[]->body,pc_guard|_::_->(* The bindings need to be in scope both in the guard and in the body. So we must
bind the bindings in both. But we must be careful, because a variable might be
used only in the guard or in the body, and we don't want spurious unused-var
warnings. We thus copy the guard into the body (to create one scope where all the
variable are used) and then ignore any unused-var warnings in the guard. *)(matchpc_guardwith|None->pexp_let~locNonrecursivebindingsbody,None|Someguard_exp->letguard_occ=Merlin_helpers.hide_expression[%exprignore([%eguard_exp]:_);assertfalse]inletbody_with_guard_occurrences=[%expriffalsethen[%eguard_occ]else[%ebody]]in(pexp_let~locNonrecursivebindingsbody_with_guard_occurrences,Some(disable_unused_var_warning(pexp_let~locNonrecursive(List.map~f:ignore_patternbindings)guard_exp))))(* ignore_pattern: we don't want merlin to see both the bindings in the guard and
in the case body *)in[{pc_lhs;pc_rhs;pc_guard}]inmatchPpxlib_jane.Jane_syntax.Pattern.of_astpatwith|Some(Jpat_tuple_fields,_attrs)->Location.raise_errorf~loc:pat.ppat_loc"labeled tuples are unsupported in [%%optional ]"|None|Some_->(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_or(pat1,pat2)->(* Just turn disjunctions into a list of individual cases with identical rhs
expressions and guards. The OCaml manual explicitly says they are evaluated and
bound left-to-right: https://v2.ocaml.org/manual/patterns.html#sss:pat-or
If the rhs expression is a lot of code, this could potentially blow up binary size,
slow down compilation, and/or hurt performance due to cache locality. But we didn't
support or-patterns for a long time, so most code already just duplicates the rhs,
and this is much easier than generating an actual or-pattern with correct bindings.
We can implement that instead if the need arises in the future.
If there is a [when]-guard, it is possible for it to be evaluated more times than
it would be in the equivalent (i.e. "fake", not-ppxified) match statement; see the
"side-effecting guards" test in ../test/ppx_optional_test.ml for more. *)ifunboxedthenLocation.raise_errorf~loc:pat.ppat_loc"or-patterns are not supported with [%%optional_u ].";rewrite_case~loc~modules_array~default_module~unboxed{pc_lhs=pat1;pc_rhs=body;pc_guard}@rewrite_case~loc~modules_array~default_module~unboxed{pc_lhs=pat2;pc_rhs=body;pc_guard}|Ppat_tuplepatts->letpatts,bindings=List.mapipatts~f:(funipatt->letmodule_=get_moduleiinget_pattern_and_bindings~loc~module_ipatt)|>List.unzipinsingle_pattern~ppat_desc:(Ppat_tuplepatts)~bindings:(List.concatbindings)|_->letpat,bindings=get_pattern_and_bindings~loc0pat~module_:modules_array.(0)insingle_pattern~ppat_desc:pat.ppat_desc~bindings);;(** 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};;(* unboxed: true <=> we're in an [optional_u] *)letreal_match~loc~unboxedt=letnew_matched_expr=rewrite_matched_exprt~wrapper:(fun~module_(_:int)expr->eapply~loc(eis_none~loc~module_)[expr])inletmodules=List.mapt.elements~f:(fun{module_;_}->module_)inletcases=List.concat_mapt.cases~f:(rewrite_case~loc~modules_array:(Array.of_listmodules)~default_module:t.default_module~unboxed)inpexp_match~locnew_matched_exprcases;;(* Represents the structure of or-patterns. *)moduleDisjunction_tree=structtype'at=|Leafof{pattern:pattern;a:'a}|Nodeof{pattern:pattern;l:'at;r:'at}letrecitert~f=matchtwith|Leaf{pattern=_;a}->fa|Node{pattern=_;l;r}->iterl~f;iterr~f;;letrecto_patternt~f=matchtwith|Leaf{pattern;a}->fpatterna|Node{pattern;l;r}->{patternwithppat_desc=Ppat_or(to_patternl~f,to_patternr~f)};;end(* 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=loc.loc_ghost}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}));;(* Take a pattern like [(Some _ | None) as x] and covert it to [Some _ as x | None as x].
This is pretty similar to the above, but subtly different enough for combining them to
be more code than it's worth. *)letinvert_fake_or_pattern_and_aliasespat1pat2aliasaliases=letrelocate_from_inner_to_outerlocsloc~merge~override_loc_ghost=List.foldlocs~init:(loc,[])~f:(fun((hd,tl)asacc)loc->letloc={loc_start=mergeloc.loc_starthd.loc_start;loc_end=mergeloc.loc_endhd.loc_end;loc_ghost=Option.valueoverride_loc_ghost~default:loc.loc_ghost}inifLocation.comparelochd=0thenaccelseloc,hd::tl)inList.fold(alias::aliases)~init:(pat1,pat2)~f:(fun(pat1,pat2)(x,pat)->letinner_loc,rev_loc_stack=List.foldpat.ppat_loc_stack~init:(pat.ppat_loc,[])~f:(fun(hd,tl)loc->loc,hd::tl)inletpat1_loc,pat1_loc_stack=relocate_from_inner_to_outerrev_loc_stack{inner_locwithloc_end=pat1.ppat_loc.loc_end;loc_ghost=true}~merge:Location.min_pos~override_loc_ghost:(Sometrue)inletpat2_loc,pat2_loc_stack=relocate_from_inner_to_outerrev_loc_stack{inner_locwithloc_start=pat2.ppat_loc.loc_start}~merge:Location.max_pos~override_loc_ghost:Nonein({ppat_desc=Ppat_alias(pat1,x);ppat_loc=pat1_loc;ppat_loc_stack=pat1_loc_stack;ppat_attributes=pat.ppat_attributes},{ppat_desc=Ppat_alias(pat2,x);ppat_loc=pat2_loc;ppat_loc_stack=pat2_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. *)letrecanalyze_fake_patternpattern:_Disjunction_tree.t=matchpattern.ppat_descwith|Ppat_any->Leaf{pattern;a=`Any}|Ppat_var_->Leaf{pattern;a=`Real}|Ppat_or(pat1,pat2)->Node{pattern;l=analyze_fake_patternpat1;r=analyze_fake_patternpat2}|Ppat_alias(pat,x)->letoriginal_alias_pattern=patterninletreclooppatternaliasaliases:_Disjunction_tree.t=matchpattern.ppat_descwith|Ppat_any|Ppat_var_->Leaf{pattern=original_alias_pattern;a=`Real}|Ppat_or(pat1,pat2)->letpat1,pat2=invert_fake_or_pattern_and_aliasespat1pat2aliasaliasesinNode{pattern=original_alias_pattern;l=analyze_fake_patternpat1;r=analyze_fake_patternpat2}|Ppat_alias(pat,x)->looppat(x,pattern)(alias::aliases)|_->Leaf{pattern=original_alias_pattern;a=`Both(split_fake_alias_patternpatternaliasaliases)}inlooppat(x,pattern)[]|_->Leaf{pattern;a=`Fake};;letrecanalyze_fake_patternspattern:_Disjunction_tree.t=matchpattern.ppat_descwith|Ppat_or(pat1,pat2)->Node{pattern;l=analyze_fake_patternspat1;r=analyze_fake_patternspat2}|Ppat_tuplepatts->Leaf{pattern;a=Array.of_list_map~f:analyze_fake_patternpatts}|_->Leaf{pattern;a=[|analyze_fake_patternpattern|]};;letmake_fake_pattern_compatibleexpr_kindpatt_tree=Disjunction_tree.to_patternpatt_tree~f:(funpatpatt_kind->matchexpr_kind,patt_kindwith|`Fake,`Fake|`Real,`Real|_,`Any->pat|`Both,((`Fake|`Real|`Both_)askind)->letwildcard=lazy(ppat_any~loc:Location.none)inletfake,real=matchkindwith|`Fake->pat,forcewildcard|`Real->forcewildcard,pat|`Bothboth->bothinppat_tuple~loc:{pat.ppat_locwithloc_ghost=true}[fake;real]|`Any,(`Fake|`Real|`Both_)|`Fake,(`Real|`Both_)|`Real,(`Fake|`Both_)->Location.raise_errorf~loc:pat.ppat_loc"Bug in [%%optional ]: this pattern is incompatible with the corresponding fake \
expression");;letmake_fake_patterns_compatibleexpr_kindspatt_tree=Disjunction_tree.to_patternpatt_tree~f:(funpatpatt_trees->matchpatt_treeswith|[|patt_tree|]->make_fake_pattern_compatibleexpr_kinds.(0)patt_tree|_->letpatts=Array.mapipatt_trees~f:(funipatt_tree->make_fake_pattern_compatibleexpr_kinds.(i)patt_tree)|>Array.to_listin{patwithppat_desc=Ppat_tuplepatts});;lettranslate_fake_match_casescases~num_exprs=letpatt_trees=Array.of_list_mapcases~f:(fun{pc_lhs=pat;_}->analyze_fake_patternspat)inletmax_num_patts=refnum_exprsinArray.iterpatt_trees~f:(funpatt_tree->Disjunction_tree.iterpatt_tree~f:(funpatt_trees->Ref.replacemax_num_patts(max(Array.lengthpatt_trees))));letexpr_kinds=Array.create~len:!max_num_patts`Anyin(* We need to ensure the fake expression can generate bindings for all of its
corresponding patterns. *)Array.iterpatt_trees~f:(funpatt_tree->Disjunction_tree.iterpatt_tree~f:(funpatt_trees->Array.iteripatt_trees~f:(funipatt_tree->Disjunction_tree.iterpatt_tree~f:(funpatt_kind->matchexpr_kinds.(i),patt_kindwith|`Both,_|`Fake,`Fake|`Real,`Real|_,`Any->()|`Any,((`Fake|`Real)asexpr_kind)->expr_kinds.(i)<-expr_kind|`Fake,`Real|`Real,`Fake|_,`Both_->expr_kinds.(i)<-`Both))));letcases=List.mapicases~f:(funccase->{casewithpc_lhs=make_fake_patterns_compatibleexpr_kindspatt_trees.(c)})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~unboxed~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}inletbindings=bindings_for_matched_exprt.elementsinletloc={match_locwithloc_ghost=true}inletbody=ifunboxedthenreal_match~loc~unboxedtelse(letfake_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 hide this branch. This also disables warnings, but
that's OK because we rely on the other match we generate for error messages. *)real_match~loc~unboxedt|>hide_exprin[%expriffalsethen[%efake_match]else[%ereal_match]])inpexp_let~locNonrecursivebindingsbody;;(* 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~unboxed~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~unboxed~match_loc:e.pexp_loc~module_);;letoptional=Extension.declare_with_path_arg"optional"Extension.Context.expressionAst_pattern.(single_expr_payload__)(expand_match~unboxed:false);;letoptional_u=Extension.declare_with_path_arg"optional_u"Extension.Context.expressionAst_pattern.(single_expr_payload__)(expand_match~unboxed:true);;let()=Driver.register_transformation"optional"~extensions:[optional]let()=Driver.register_transformation"optional_u"~extensions:[optional_u](* The fake match built above doesn't work for unboxed types. So [optional_u] doesn't
generate it. *)