Implement parsing for optional shape fields
[hiphop-php.git] / hphp / hack / src / hh_matcher / matcher.ml
blob6028f1c0a54864c34c13edd95cf3033ed5acb8b8
1 (**
2 * Copyright (c) 2015, Facebook, Inc.
3 * All rights reserved.
5 * This source code is licensed under the BSD-style license found in the
6 * LICENSE file in the "hack" directory of this source tree. An additional grant
7 * of patent rights can be found in the PATENTS file in the same directory.
9 *)
11 open Ast
12 open Patcher
13 open Hh_match_utils
15 module List = Core_list
17 (* ========================= TYPES =========================== *)
19 (* for processing php identifiers such that we can use them
20 to denote regex special characters *)
21 type id_type =
22 (* no special pattern commands, just a normal identifier to match *)
23 | Normal of string
24 (* not and or are modifiers to an identifier, so the
25 identifier is still relevant *)
26 | Not of string
27 | Or of string
29 (* Identifier is not relevant for these *)
30 | Wildcard
31 | WildcardName
32 | DoNotCare
33 | RegExp
35 | SkipAnyTok
36 | KStar
38 type metavar_tgt =
39 | NodeList of ast_node list
40 | Literal of string
42 type skipany_ast_node =
43 | SkipanyBlock of block
44 | SkipanyExpr of expr
46 (* Put patches in a set because it is common for many of the same
47 patch to be put in the environment when patterns include SkipAny
48 or KStar *)
49 module PatchSet =
50 Set.Make(
51 struct
52 type t = patch
53 let compare =
54 (fun p1 p2 ->
55 if p1.Hh_match_utils.start_loc <> p2.Hh_match_utils.start_loc
56 then p1.Hh_match_utils.start_loc - p2.Hh_match_utils.start_loc
57 else p1.Hh_match_utils.end_loc - p2.Hh_match_utils.end_loc)
58 end)
60 module MetavarMap = Map.Make(String)
62 (* Encapsulated global state of the matcher which can
63 keep track of metavariables used in matching *)
64 type matcher_env = {
65 (* references to the text file, source*)
66 file : Relative_path.t;
67 source : string;
68 (* things to accumulate as we match *)
69 uses_regexp : bool;
70 comments : (Pos.t * string) list;
71 metavars : metavar_tgt MetavarMap.t;
72 (* optional pieces related to patching *)
73 transformations : patch_maps;
74 patches : PatchSet.t;
77 (* We don't use a set for result because only time we get duplicates
78 is when merging results from different branches, and there will be
79 distinct matches starting at the same position with nontrivial operations
80 to check for equality. *)
81 type match_result =
82 | Matches of (ast_node * File_pos.t) list
83 | NoMatch
85 let dummy_success_res = Matches [(DummyNode, File_pos.dummy)]
87 (* for SkipAny matching *)
88 let star_stmt =
89 Ast.Expr (Pos.none, (String (Pos.none, "__KSTAR")))
91 let is_skip_any_stmt = function
92 | Ast.Expr (_, (String (_, "__DUMMYSKIPANY"))) -> true
93 | _ -> false
95 (* ======================== HELPERS ======================== *)
97 (* should merge the two environments (mainly for correct remembering of
98 metavariable bindings with .* type matches and patches that need to
99 be applied). May result in binding multiple values to the same
100 metavariable. *)
101 let merge_envs
102 (env1 : matcher_env)
103 (env2 : matcher_env) : matcher_env =
104 { env1 with patches = PatchSet.union env1.patches env2.patches }
106 (* small helper to return the correct env depending on whether
107 the ret passed in is a fail or not (when matches fail we should revert
108 the env to what it was before the failed match to make sure it is correct) *)
109 let revert_env_if_no_match
110 (ret : match_result * matcher_env)
111 (old_env : matcher_env) : (match_result * matcher_env) =
112 match fst ret with
113 | NoMatch -> (NoMatch, old_env)
114 | _ -> ret
116 let _contains_match = function
117 | Matches _ -> true
118 | _ -> false
120 (* For when we concatenate matches from SkipAny or KStar where there may be
121 duplicates.
122 TODO: implement some kind of duplicate-removal system that works;
123 position-based deduplication does not work *)
124 let concat_match_results_nodup
125 (results : match_result list) : match_result =
126 List.fold_left
127 ~f:(fun res_acc new_res ->
128 match res_acc, new_res with
129 | Matches matches_so_far , Matches new_matches ->
130 Matches (List.rev_append new_matches matches_so_far)
131 | Matches _, NoMatch -> res_acc
132 | NoMatch, Matches _ -> new_res
133 | NoMatch, NoMatch -> NoMatch)
134 ~init:NoMatch
135 results
137 let concat_match_results (results : match_result list) : match_result =
138 List.fold_left
139 ~f:(fun res_acc new_res ->
140 match res_acc, new_res with
141 | Matches matches_so_far , Matches new_matches ->
142 Matches (matches_so_far @ new_matches)
143 | Matches _, NoMatch -> res_acc
144 | NoMatch, Matches _ -> new_res
145 | NoMatch, NoMatch -> NoMatch)
146 ~init:NoMatch
147 results
149 let find_substring to_search substring =
150 let re = Str.regexp_string substring in
151 try Str.search_forward re to_search 0
152 with Not_found -> -1
154 let remove_keyword str keyword keyword_start =
155 let keylen = String.length keyword in
156 let len = String.length str in
157 (String.sub str 0 keyword_start) ^
158 (String.sub str (keyword_start + keylen) (len - keyword_start - keylen))
160 (* Returns a string corresponding to the metavariable name
161 if the identifier is a metavariable *)
162 let check_mvar (id : string) : string option =
163 let found_meta_key = find_substring id "META" in
164 if found_meta_key = -1
165 then None
166 else
167 (* get everything after the "META" *)
168 Some (String.sub
169 id (found_meta_key + 4)
170 (String.length id - (found_meta_key + 4)))
172 (* Instantiates an identifier metavariable (or returns the identifier if it
173 is not a metavar*)
174 let instantiate_mvar_id (env : matcher_env) (id : string) : string =
175 let mvar_id = check_mvar id in
176 match mvar_id with
177 | None -> id
178 | Some mvar_name ->
180 match MetavarMap.find mvar_name env.metavars with
181 | Literal str -> str
182 | _ -> id
183 with Not_found -> failwith ("metavar " ^ mvar_name ^ " not found")
185 let add_mvar_id
186 (env : matcher_env)
187 (id : string)
188 (binding : string) : matcher_env =
189 match check_mvar id with
190 | None -> env
191 | Some mvar ->
192 if env.uses_regexp
193 then failwith
194 "Regular expressions and metavariables can't be used in the same pattern"
195 else
196 (* Add binding if one does not exist,
197 WILL NOT OVERWRITE EXISTING BINDING *)
198 { env with
199 metavars =
200 if MetavarMap.mem mvar env.metavars then env.metavars
201 else MetavarMap.add mvar (Literal binding) env.metavars }
203 (* Expr is a metavariable if it matches "__.*META.*" (can contain other
204 keywords, the part after the meta is the metavariable name) *)
205 let is_meta_expr = function
206 | (_, (String (_, str))) -> check_mvar str
207 | _ -> None
209 (* NOTE: it is fine that meta exprs and stmts are the same because we check
210 stmt before expr.*)
211 let is_meta_stmt = function
212 | Ast.Expr e -> is_meta_expr e
213 | _ -> None
215 let add_mvar_node
216 (type a)
217 (env : matcher_env)
218 (is_meta_fn : a -> string option)
219 (node : a)
220 (binding : ast_node) : matcher_env =
221 match is_meta_fn node with
222 | None -> env
223 | Some mvar_name ->
224 if MetavarMap.mem mvar_name env.metavars then env
225 else { env with
226 metavars =
227 MetavarMap.add mvar_name (NodeList [binding]) env.metavars }
229 (* Override the ast_constructor's relevant methods to instantiate metavariables
230 whenever one is found.
231 NOTE: will instantiate ALL metavariables that are a children of the node that
232 you start on *)
233 class metavar_instantiating_ctr (env : matcher_env) = object
234 inherit [unit] AstConstructor.ast_constructor as super
236 val mvar_map = env.metavars;
237 val match_env = env;
239 method! on_stmt env s = begin
240 match is_meta_stmt s with
241 | Some mvar_name -> begin
243 let mval = MetavarMap.find mvar_name mvar_map in
244 match mval with
245 | NodeList vals ->
246 (* Verify that the list is homogenous, If it is,
247 replace the statement with it *)
248 if List.exists
249 ~f:(function
250 | Hh_match_utils.Stmt _ -> false
251 | _ -> true) vals
252 then super#on_stmt env s
253 else
254 List.map
255 ~f:(function
256 | Hh_match_utils.Stmt mvar_val -> mvar_val
257 | _ ->
258 failwith ("should never happen unless you" ^
259 " metavariables are broken")) vals
260 | _ -> super#on_stmt env s
261 with Not_found -> super#on_stmt env s end
262 | _ -> super#on_stmt env s
265 method! on_expr env e = begin
266 match is_meta_expr e with
267 | Some mvar_name -> begin
269 let mval = MetavarMap.find mvar_name mvar_map in
270 match mval with
271 | NodeList vals ->
272 if List.exists
273 ~f:(function
274 | Hh_match_utils.Expr _ -> false
275 | _ -> true) vals
276 then super#on_expr env e
277 else
278 List.map
279 ~f:(function
280 | Hh_match_utils.Expr mvar_val -> mvar_val
281 | _ -> failwith ("should never happen unless you" ^
282 " metavariables are broken")) vals
283 | _ -> super#on_expr env e
284 with Not_found -> super#on_expr env e end
285 | _ -> super#on_expr env e
288 method! on_id env id = begin
289 let id = super#on_id env id in
290 fst id, instantiate_mvar_id match_env (snd id)
293 method! on_pstring env pstr = begin
294 let pstr = super#on_pstring env pstr in
295 fst pstr, instantiate_mvar_id match_env (snd pstr)
299 (* turns a target node into text that can be put into a patch, instantiating
300 any metavariables contained and unparsing the stmt *)
301 let stmt_to_text env stmts =
302 (* instantiate relevant metavariables *)
303 stmts |>
304 List.map
305 ~f:((new metavar_instantiating_ctr env)#on_stmt ()) |>
306 List.concat |> Unparser.Unparse.u_naked_block |>
307 Unparsed.to_string
309 (* see comment on stmt_to_text *)
310 let expr_to_text env exprs =
311 (* instantiate relevant metavariables *)
312 exprs |>
313 List.map
314 ~f:((new metavar_instantiating_ctr env)#on_expr ()) |>
315 List.concat |>
316 (fun expr_list -> Expr_list expr_list) |>
317 Unparser.Unparse.u_expr_ |>
318 Unparsed.to_string
320 let instantiate_mvar_stmt env stm =
321 match is_meta_stmt stm with
322 | None -> stm
323 | Some _ ->
324 AstConstructor.list_to_single
325 (new metavar_instantiating_ctr env)#on_stmt () stm
327 let instantiate_mvar_expr env exp =
328 match is_meta_expr exp with
329 | None -> exp
330 | Some _ ->
331 AstConstructor.list_to_single
332 (new metavar_instantiating_ctr env)#on_expr () exp
334 (* Will get all non-metavariable keywords *)
335 let process_identifier (id : string) : id_type =
336 let not_keyword = "__NOT" in
337 let not_action = function key -> Not (remove_keyword id not_keyword key) in
338 let or_keyword = "__OR" in
339 let or_action = function key -> Or (remove_keyword id or_keyword key) in
340 let regexp_keyword = "__REGEXP" in
341 let regexp_action = function _ -> RegExp in
342 let wildcard_keyword = "__ANY" in
343 let wildcard_action = function _ -> Wildcard in
344 let wildcard_name_keyword = "__SOMENAME" in
345 let wildcard_name_action = function _ -> WildcardName in
346 let do_not_care_keyword = "__SOMENODE" in
347 let do_not_care_action = function _ -> DoNotCare in
348 let skip_any_keyword = "__SKIPANY" in
349 let skip_any_action = function _ -> SkipAnyTok in
350 let star_keyword = "__KSTAR" in
351 let star_action = function _ -> KStar in
352 let actions =
353 [not_keyword, not_action; or_keyword, or_action;
354 regexp_keyword, regexp_action;
355 wildcard_keyword, wildcard_action;
356 wildcard_name_keyword, wildcard_name_action;
357 do_not_care_keyword, do_not_care_action;
358 skip_any_keyword, skip_any_action;
359 star_keyword, star_action] in
360 let rec check_keywords = function
361 (* default to normal identifier *)
362 | [] -> Normal id
363 | (keyword, action) :: tail ->
364 let substr_pos = find_substring id keyword in
365 if substr_pos <> -1
366 then (action substr_pos)
367 else check_keywords tail in
368 check_keywords actions
370 (* for readability these are left *)
371 let get_id_type (id : (Pos.t * string)) : id_type =
372 process_identifier (snd id)
374 let match_string
375 (t_s : string)
376 (p_s : string)
377 (env : matcher_env) : (match_result * matcher_env) =
378 let env' = add_mvar_id env p_s t_s in
379 let p_s = instantiate_mvar_id env' p_s in
380 let match_strings str1 str2 =
381 let remove_leading_slash str =
382 let len = String.length str in
383 if len > 1 && str.[0] = '\\'
384 then String.sub str 1 (len - 1)
385 else str in
386 (remove_leading_slash str1) = (remove_leading_slash str2) in
387 let is_match =
388 match process_identifier p_s with
389 | Wildcard | WildcardName | KStar -> true
390 | Or p_str | Normal p_str -> match_strings t_s p_str
391 | Not p_str -> not (match_strings t_s p_str)
392 (* this should be handled by match_regexp *)
393 | RegExp -> failwith "Regular expressions should be handled in match_regexp"
394 (* haven't implemented these things *)
395 | _ -> false in
396 if is_match
397 then dummy_success_res, env'
398 else NoMatch, env
400 let match_regexp
401 (t_id : Pos.t * string)
402 (p_id : Pos.t * string)
403 (env : matcher_env) : (match_result * matcher_env) =
404 let env' = {env with uses_regexp = true} in
405 if not (MetavarMap.is_empty env.metavars)
406 then failwith
407 "Regular expressions and metavariables can't be used in the same pattern"
408 else
409 let p_line = Pos.line (fst p_id) in
410 let is_same_line_as_pattern comment = (Pos.line (fst comment) = p_line) in
411 let comment = List.find env.comments is_same_line_as_pattern in
412 let remove_leading_slash str =
413 let len = String.length str in
414 if len > 1 && str.[0] = '\\'
415 then String.sub str 1 (len - 1)
416 else str in
417 let text = remove_leading_slash (snd t_id) in
418 match comment with
419 | None -> NoMatch, env'
420 | Some (_, comment) ->
421 if Str.string_match (Str.regexp comment) text 0
422 then dummy_success_res, env'
423 else NoMatch, env'
426 (* for use in matching lists and readibility in matching functions
427 has a similar signature to other match methods *)
428 let match_id_res
429 (t_id : Pos.t * string)
430 (p_id : Pos.t * string)
431 (env : matcher_env) : (match_result * matcher_env) =
432 let res, env' =
433 match process_identifier (snd p_id) with
434 | RegExp -> match_regexp t_id p_id env
435 | _ -> match_string (snd t_id) (snd p_id) env
437 match res with
438 | Matches _ -> (Matches [(DummyNode, Pos.pos_start (fst t_id))], env')
439 | NoMatch -> NoMatch, env
441 (* acc is the match results from the child node *)
442 let update_res_with
443 (res : match_result)
444 (node : ast_node)
445 (node_pos : File_pos.t) : match_result =
446 match res with
447 | Matches so_far -> Matches ((node, node_pos) :: so_far)
448 | NoMatch -> Matches [node, node_pos]
450 (* for finding the __SKIPANY string in the file *)
451 let is_skip_any_unproc (elem : stmt) =
452 match elem with
453 | Ast.Expr (_,(String pstr)) -> get_id_type pstr = SkipAnyTok
454 | _ -> false
456 (* list of stmts that correspond to blocks *)
457 type block_accum = block list
459 (* return of on_block will be an accumulator of all the blocks in reverse
460 order of how they were found (ending with the block given to it).
461 i.e. order is last :: 2nd last :: ... :: block given *)
462 class block_finding_visitor () =
463 object
464 inherit [block_accum] Ast_visitor.ast_visitor as super
466 method! on_block acc block =
467 begin
468 let acc = block :: acc in
469 super#on_block acc block;
473 (* used to get (in reverse order) all expressions in a file *)
474 class expr_finding_visitor () =
475 object
476 inherit [expr list] Ast_visitor.ast_visitor as super
478 method! on_expr acc exp =
479 begin
480 let acc = exp :: acc in
481 super#on_expr acc exp;
485 (* these functions must be in this module because I am passing them arguments
486 of multiple different types (match list is used on def list, stmt list etc)*)
487 module LM =
488 struct
489 (* TODO: implement disjunction matching correctly - compare with all
490 possible elements of the disjunction, if any of them match,
491 count as match. Simple, but requires preprocessing of the
492 input string for disjunction handling (or just a special case
493 that continues to consume until you are out of disjunction cases *)
494 (* Try to match a list of ast nodes. If paired with the correct handle_sa_fn
495 and handle_sa_hd_fn will also match SkipAny statements. *)
496 let match_list_with_skips (type a)
497 (is_star_fn : a -> bool)
498 (* for stmts, exprs for being able to make mvars out of
499 lists of nodes *)
500 (is_meta_fn : (a -> string option) option)
501 (to_node_fn : (a -> ast_node) option)
502 (match_elem_fn : a -> a -> matcher_env -> match_result * matcher_env)
503 (* handles checking for and preprocessing SkipAny tokens as well as
504 matching processed __SKIPANYDUMMY tokens when they are removed from
505 the pattern list *)
506 (handle_sa_fn :
507 (a list -> a list -> a list -> matcher_env ->
508 match_result * matcher_env) ->
509 a list -> a list -> a list -> matcher_env ->
510 match_result * matcher_env)
511 (* handles the case when our pattern starts with a SkipAny, like the
512 case where our pattern starts with a KStar. None means the pattern
513 is not a SkipAny, Some NoMatch means the pattern was a SkipAny that
514 failed to match *)
515 (handle_sa_hd_fn :
516 (a list -> a list -> a list -> matcher_env ->
517 match_result * matcher_env) ->
518 a -> a list -> a list -> a list -> matcher_env ->
519 (match_result * matcher_env) option)
520 (t_list : a list)
521 (p_list : a list)
522 (env0 : matcher_env) : match_result * matcher_env =
523 (* for case where we have some of both text and pattern left
524 e.g. text = [stmt, ...] pattern = [stmt, ...]*)
525 let rec try_match_heads
526 (t_hd : a)
527 (t_tl : a list)
528 (p_hd : a)
529 (p_tl : a list)
530 (skipped_text : a list)
531 (env : matcher_env) :
532 match_result * matcher_env =
533 (* If the pattern starts with a SkipAny, we want to handle that. *)
534 let skipany_ret =
535 handle_sa_hd_fn
536 try_match_lists p_hd t_tl p_tl (t_hd :: skipped_text) env in
537 match skipany_ret with
538 (* if we handled a SkipAny, we want to pass that result back, it has
539 already matched all the rest of the pattern (including the part
540 after the SkipAny *)
541 | Some ret -> ret
542 (* Otherwise that call did nothing and we need to continue matching *)
543 | None ->
544 (* check for pattern, text match *)
545 let (elt_match, env') = match_elem_fn t_hd p_hd env in
546 if elt_match = NoMatch
547 (* e.g. text = [stmt1, ...] pattern = [stmt2, ...] *)
548 then NoMatch, env
549 else
550 let res, env' =
551 handle_sa_fn try_match_lists t_tl p_tl skipped_text env' in
552 match res with
553 (* first elements don't match:
554 e.g. text = [stmt1, ...] pattern = [stmt2, ...] *)
555 | NoMatch -> NoMatch, env
556 (* first elements match:
557 e.g. text = [stmt1, ...] pattern = [stmt1, ...] *)
558 | Matches _ ->
559 concat_match_results_nodup [elt_match; res], env'
561 (* ========== functions for if a kleene star is encountered ============*)
562 (* consume all leading KStars from the pattern *)
563 and consume_stars (p_list : a list) : a list * string option =
564 match p_list with
565 | hd :: tl when is_star_fn hd ->
566 let res = consume_stars tl in
567 let meta_keyword = match snd res with
568 | Some key -> Some key
569 | None ->
570 match is_meta_fn with
571 | Some is_meta_fn -> is_meta_fn hd
572 | None -> None in
573 fst res, meta_keyword
574 | _ -> p_list, None
576 (* try the rest of the pattern based off all possible amounts of text the
577 kleene star could consume by recursing with it removing 0, 1, 2 ...
578 elements from the text *)
579 and handle_star_helper
580 (t_list : a list)
581 (p_list : a list)
582 (skipped_text : a list)
583 (env : matcher_env)
584 (meta_key : string option) :
585 match_result * matcher_env =
586 let add_mvar (env : matcher_env) : matcher_env =
587 match meta_key, to_node_fn with
588 | Some key, Some to_node_fn ->
589 if MetavarMap.mem key env.metavars then env
590 else
591 let mapped_nodes = List.map ~f:to_node_fn skipped_text in
592 { env with
593 metavars =
594 MetavarMap.add
595 key (NodeList (List.rev mapped_nodes)) env.metavars }
596 | _, _ -> env in
597 match t_list with
598 | [] ->
599 revert_env_if_no_match
600 (try_match_lists [] p_list skipped_text (add_mvar env)) env
601 | t_hd :: t_tl ->
602 let rec_res, rec_env =
603 handle_star_helper
604 t_tl p_list (t_hd :: skipped_text) env meta_key in
605 let norec_res, norec_env =
606 try_match_lists t_list p_list skipped_text (add_mvar env) in
607 match rec_res, norec_res with
608 | NoMatch, NoMatch -> NoMatch, env
609 | Matches _, NoMatch -> rec_res, rec_env
610 | NoMatch, Matches _ -> norec_res, norec_env
611 | Matches _, Matches _ ->
612 (concat_match_results_nodup [rec_res; norec_res],
613 merge_envs rec_env norec_env)
615 (* consumes the leading KStars from the pattern then tries to do matching,
616 returning the results of all successful matches with different amounts
617 of text consumed by the KStar *)
618 and handle_star_match
619 (t_list : a list)
620 (p_list : a list)
621 (_skipped_text : a list)
622 (env : matcher_env) :
623 match_result * matcher_env =
624 let p_list, meta_key = consume_stars p_list in
625 handle_star_helper t_list p_list [] env meta_key
627 (*========================= end kstar fns ==========================*)
629 (* handles trying to match the lists that are our pattern and text
630 Used for base cases to terminate the recursion when we can determine
631 from list structure if a match cannot be made. Otherwise calls the
632 correct function to match the lists *)
633 and try_match_lists
634 (t_list : a list)
635 (p_list : a list)
636 (skipped_text : a list)
637 (env : matcher_env) :
638 match_result * matcher_env =
639 match t_list, p_list with
640 (* Pattern starst with a KStar
641 e.g. text = [] pattern = [".*", ...] *)
642 | _, p_hd :: _ when is_star_fn p_hd ->
643 handle_star_match t_list p_list [] env
644 (* we're out of text, pattern must be empty *)
645 | [], [] -> dummy_success_res, env
646 (* e.g. text = [] pattern = [stmt1, ...] *)
647 | [], _ -> NoMatch, env
648 (* more text, pattern must match *)
649 (* e.g. text = [stmt1, ...] pattern = [] *)
650 | _t_hd :: _t_tl, [] -> NoMatch, env
651 (* normal case of having both text, pattern left *)
652 | t_hd :: t_tl, p_hd :: p_tl ->
653 revert_env_if_no_match
654 (try_match_heads t_hd t_tl p_hd p_tl skipped_text env) env in
655 revert_env_if_no_match
656 (handle_sa_fn try_match_lists t_list p_list [] env0) env0
658 (* these two functions are used for normal list matching that does not have
659 to handle SkipAny tokens in the pattern *)
660 let dummy_act_if_skipany
661 (_try_match_star_fn :
662 'a list -> 'a list -> 'a list -> matcher_env ->
663 match_result * matcher_env)
664 (_p_hd : 'a)
665 (_t_tl : 'a list)
666 (_p_tl : 'a list)
667 (_skipped_text : 'a list)
668 (_env : matcher_env) : (match_result * matcher_env) option =
669 None
671 let try_match_dummy
672 (try_match_star_fn :
673 'a list -> 'a list -> 'a list -> matcher_env ->
674 match_result * matcher_env)
675 (t_list : 'a list)
676 (p_list : 'a list)
677 (skipped_text : 'a list)
678 (env : matcher_env) :
679 match_result * matcher_env =
680 try_match_star_fn t_list p_list skipped_text env
682 (* normal list matching (not handling SkipAny cases *)
683 let match_list (type a)
684 (is_star_fn : a -> bool)
685 (* for stmts, exprs for being able to make mvars out of
686 lists of nodes *)
687 ?(is_meta_fn = None)
688 ?(to_node_fn: (a -> ast_node) option = None)
689 (match_elem_fn : a -> a -> matcher_env -> match_result * matcher_env)
690 (t_list : a list)
691 (p_list : a list)
692 (env0 : matcher_env) : match_result * matcher_env =
693 match_list_with_skips
694 is_star_fn
695 is_meta_fn
696 to_node_fn
697 match_elem_fn
698 try_match_dummy
699 dummy_act_if_skipany
700 t_list
701 p_list
702 env0
704 let match_option
705 (match_fn : 'a -> 'a -> matcher_env -> (match_result * matcher_env))
706 (t_opt : 'a option)
707 (p_opt : 'a option)
708 (env : matcher_env) :
709 (match_result * matcher_env) =
710 match p_opt, t_opt with
711 | Some p_opt, Some t_opt ->
712 match_fn t_opt p_opt env
713 | None, Some _
714 | Some _, None -> (dummy_success_res, env)
715 | None, None ->
716 dummy_success_res, env
718 (* because lists of pairs occur fairly often *)
719 let match_pair_fn
720 (match_fn1 : 'a -> 'a -> matcher_env -> (match_result * matcher_env))
721 (match_fn2 : 'b -> 'b -> matcher_env -> (match_result * matcher_env)):
722 ('a * 'b -> 'a * 'b -> matcher_env -> match_result * matcher_env) =
723 (fun (t_vals : 'a * 'b) (p_vals : 'a * 'b) (env : matcher_env) ->
724 let old_env = env in
725 let t_v1, t_v2 = t_vals in
726 let p_v1, p_v2 = p_vals in
727 let v1_res, env = match_fn1 t_v1 p_v1 env in
728 match v1_res with
729 | NoMatch -> NoMatch, old_env
730 | Matches _ ->
731 let v2_res, env = match_fn2 t_v2 p_v2 env in
732 match v2_res with
733 | NoMatch -> NoMatch, old_env
734 | Matches _ ->
735 (concat_match_results [v1_res; v2_res]), env)
737 (* matches all the different attributes of a node given a list, short
738 circuits and returns NoMatch and the original environment if any of
739 the matches fail *)
740 let rec match_attributes
741 (actions : (matcher_env -> match_result * matcher_env) list)
742 (env0 : matcher_env) : match_result * matcher_env =
743 match actions with
744 | [] -> dummy_success_res, env0
745 | action :: tl ->
746 let res, env = action env0 in
747 match res with
748 | NoMatch -> NoMatch, env0
749 | Matches _ ->
750 let tl_res, env = match_attributes tl env in
751 match tl_res with
752 | NoMatch -> NoMatch, env0
753 | Matches _ ->
754 (concat_match_results [res; tl_res]), env
756 (* checks if a patch should be created based on the env (transformation
757 list), the pattern node and creates it if necessary *)
758 let patch_if_necc
759 (type a)
760 (text_elem : a)
761 (pat_elem : a)
762 (adjust_fn : string -> int -> int -> int * int)
763 (delete_list : a list)
764 (* assoc list from pattern -> target *)
765 (transf_list : (a * a list) list)
766 (node_to_txt_fn : matcher_env -> a list -> string)
767 (extent_find_fn : a -> File_pos.t * File_pos.t)
768 (ret : match_result * matcher_env) : (match_result * matcher_env) =
769 let res, env = ret in
770 let create_patch result_str =
771 let elem_ext = extent_find_fn text_elem in
772 let newpatch =
773 { start_loc = File_pos.offset (fst elem_ext);
774 end_loc = File_pos.offset (snd elem_ext);
775 result_str;
776 range_adjustment_fn = adjust_fn } in
777 res, { env with patches = PatchSet.add newpatch env.patches } in
778 (* check if the element is in the list of nodes to be deleted*)
779 if List.exists ~f:(fun to_del -> pat_elem == to_del) delete_list
780 then
781 create_patch ""
782 (* check if we have a relevant transformation*)
783 else
785 let tgt_node = List.assq pat_elem transf_list in
786 create_patch (node_to_txt_fn env tgt_node)
787 with Not_found -> ret
790 (* =================== MATCHING FUNCTIONS ==================== *)
791 (* All match functions take 2 Ast nodes of the same type and try to match them.
792 The matching is sensitive to wildcards and will recursively try to match
793 child nodes on the ast.
795 First it checks for wildcards, metavariables and other special cases that
796 allow us to do regex-type matching.
797 Then it matches by type: e.g. if we have two def nodes they should both be
798 type Fun_ or both type Class_ etc.
799 Then it checks if all attributes of each node are the same, calling the
800 proper match methods if necessary. If any of these matches for its children
801 fail, then the match returns failure.
802 e.g. when matching a fun_ we check the name then match the parameters, if
803 those succeed then we try matching the body etc.
805 The result returned is the result which is a list of matches containing the
806 node that was matched and the position of the beginning of the element
807 matched. If part of the text was matched with a kleene star, no matches
808 will be added to the list. (A dummy success value may be returned if
809 necessary). The modified matching environemnt (or the original environment
810 if it wasn't modified or the match failed) *)
811 let rec match_ast_nodes
812 (text_node : ast_node)
813 (pattern_node : ast_node)
814 (env : matcher_env) :
815 (match_result * matcher_env) =
816 match text_node, pattern_node with
817 | Program t_prog, Program p_prog ->
818 match_program t_prog p_prog env
819 | Def t_def, Def p_def ->
820 match_def t_def p_def env
821 | Fun_ t_fun_, Fun_ p_fun_->
822 match_fun_ t_fun_ p_fun_ env
823 | Tparam t_tparam, Tparam p_tparam ->
824 match_tparam t_tparam p_tparam env
825 | Fun_param t_fun_param, Fun_param p_fun_param ->
826 match_fun_param t_fun_param p_fun_param env
827 | Class_ t_class_, Class_ p_class_ ->
828 match_class_ t_class_ p_class_ env
829 | Class_elt t_class_elt, Class_elt p_class_elt ->
830 match_class_elt t_class_elt p_class_elt env
831 | Hh_match_utils.Method t_method_, Hh_match_utils.Method p_method_ ->
832 match_method_ t_method_ p_method_ env
833 | Hh_match_utils.Stmt t_stmt, Hh_match_utils.Stmt p_stmt ->
834 match_stmt t_stmt p_stmt env
835 | Hh_match_utils.Expr t_expr, Hh_match_utils.Expr p_expr ->
836 match_expr t_expr p_expr env
837 | DummyNode, _ | _, DummyNode | _, Nodes _ | Nodes _, _ ->
838 failwith "cannot match a non-AST construct"
839 | _, _ -> (NoMatch, env)
841 and match_program
842 (t_program : program)
843 (p_program : program)
844 (env : matcher_env) : (match_result * matcher_env) =
845 (* TODO add ability for def-lv skip_any *)
846 LM.match_list is_star_def match_def t_program p_program env
848 and match_tconstraint
849 (t_tconstraint : tconstraint)
850 (p_tconstraint : tconstraint)
851 (env : matcher_env) : (match_result * matcher_env) =
852 LM.match_option match_hint t_tconstraint p_tconstraint env
854 and match_typedef_kind
855 (t_typedef_kind : typedef_kind)
856 (p_typedef_kind : typedef_kind)
857 (env : matcher_env) : (match_result * matcher_env) =
858 match t_typedef_kind, p_typedef_kind with
859 | Alias t_h, Alias p_h
860 | NewType t_h, NewType p_h -> match_hint t_h p_h env
861 | _, _ -> NoMatch, env
863 and match_expr_list el1 el2 env =
864 LM.match_list
865 is_star_expr
866 match_expr
867 ~is_meta_fn:(Some is_meta_expr)
868 ~to_node_fn:(Some (fun exp -> Hh_match_utils.Expr exp))
869 el1 el2 env
871 and match_user_attribute
872 (t_user_attribute : user_attribute)
873 (p_user_attribute : user_attribute)
874 (env : matcher_env) : (match_result * matcher_env) =
875 LM.match_attributes
876 [match_id_res t_user_attribute.ua_name p_user_attribute.ua_name;
877 match_expr_list
878 t_user_attribute.ua_params
879 p_user_attribute.ua_params]
882 (* TODO maybe allow wildcarding namespaces *)
883 (* ignores ns_uses because that doesn't seem relevant for the matcher*)
884 and match_namespace
885 (t_namespace : Namespace_env.env)
886 (p_namespace : Namespace_env.env)
887 (env : matcher_env) : (match_result * matcher_env) =
888 match t_namespace.Namespace_env.ns_name,
889 p_namespace.Namespace_env.ns_name with
890 | Some t_name, Some p_name when t_name = p_name -> dummy_success_res, env
891 | None, None -> dummy_success_res, env
892 | _, _ -> NoMatch, env
894 and match_typedef
895 (t_typedef : typedef)
896 (p_typedef : typedef)
897 (env : matcher_env) : (match_result * matcher_env) =
898 LM.match_attributes
899 [match_id_res t_typedef.t_id p_typedef.t_id;
900 LM.match_list
901 is_star_tparam
902 match_tparam
903 t_typedef.t_tparams
904 p_typedef.t_tparams;
905 match_tconstraint t_typedef.t_constraint p_typedef.t_constraint;
906 match_typedef_kind t_typedef.t_kind p_typedef.t_kind;
907 LM.match_list
908 is_star_user_attribute
909 match_user_attribute
910 t_typedef.t_user_attributes
911 p_typedef.t_user_attributes;
912 match_namespace t_typedef.t_namespace p_typedef.t_namespace]
915 and match_cst_kind
916 (t_cst_kind : cst_kind)
917 (p_cst_kind : cst_kind)
918 (env : matcher_env) : (match_result * matcher_env) =
919 if t_cst_kind = p_cst_kind
920 then dummy_success_res, env
921 else NoMatch, env
923 and match_gconst
924 (t_gconst : gconst)
925 (p_gconst : gconst)
926 (env : matcher_env) : (match_result * matcher_env) =
927 LM.match_attributes
928 [match_cst_kind t_gconst.cst_kind p_gconst.cst_kind;
929 match_id_res t_gconst.cst_name p_gconst.cst_name;
930 LM.match_option match_hint t_gconst.cst_type p_gconst.cst_type;
931 match_expr t_gconst.cst_value p_gconst.cst_value;
932 match_namespace t_gconst.cst_namespace p_gconst.cst_namespace]
935 and match_def
936 (t_def : def)
937 (p_def : def)
938 (env : matcher_env) : (match_result * matcher_env) =
939 match t_def, p_def with
940 | Fun t_fun_, Fun p_fun_ ->
941 match_fun_ t_fun_ p_fun_ env
942 | Class t_class_, Class p_class_ ->
943 match_class_ t_class_ p_class_ env
944 | Ast.Stmt t_stmt, Ast.Stmt p_stmt->
945 match_stmt t_stmt p_stmt env
946 | Typedef t_typedef, Typedef p_typedef ->
947 match_typedef t_typedef p_typedef env
948 | Constant t_gconst, Constant p_gconst ->
949 match_gconst t_gconst p_gconst env
950 | Namespace (t_id, t_program), Namespace (p_id, p_program) ->
951 LM.match_attributes
952 [match_id_res t_id p_id;
953 match_program t_program p_program]
955 (* I spent the last 10 minutes trying to figure out WTF this matcher is doing
956 and perhaps how to even just strip the namespace kind, but it doesn't
957 typecheck and I don't care. I don't think anyone is even using this
958 code. If you care, you can fix it. *)
960 | NamespaceUse t_iil, NamespaceUse p_iil ->
961 revert_env_if_no_match
962 (LM.match_list
963 (fun _ -> false)
964 (LM.match_pair_fn match_id_res match_id_res)
965 t_iil
966 p_iil
967 env)
970 | _, _ -> NoMatch, env
972 and match_bool
973 (t_b : bool)
974 (p_b : bool)
975 (env : matcher_env) : (match_result * matcher_env) =
976 if t_b == p_b
977 then dummy_success_res, env
978 else NoMatch, env
980 (* ignores f_mode, f_mtime *)
981 and match_fun_
982 (t_fun_ : fun_)
983 (p_fun_ : fun_)
984 (env : matcher_env) : (match_result * matcher_env) =
985 let attributes =
986 [match_id_res t_fun_.f_name p_fun_.f_name;
987 LM.match_list
988 is_star_tparam
989 match_tparam
990 t_fun_.f_tparams
991 p_fun_.f_tparams;
992 LM.match_option match_hint t_fun_.f_ret p_fun_.f_ret;
993 match_bool t_fun_.f_ret_by_ref p_fun_.f_ret_by_ref;
994 LM.match_list
995 is_star_fun_param
996 match_fun_param
997 t_fun_.f_params
998 p_fun_.f_params;
999 LM.match_list
1000 is_star_user_attribute
1001 match_user_attribute
1002 t_fun_.f_user_attributes
1003 p_fun_.f_user_attributes;
1004 match_fun_kind t_fun_.f_fun_kind p_fun_.f_fun_kind;
1005 match_namespace t_fun_.f_namespace p_fun_.f_namespace;
1006 match_stmt (Block t_fun_.f_body) (Block p_fun_.f_body)] in
1007 match get_id_type t_fun_.f_name with
1008 | Wildcard -> dummy_success_res, env
1009 | DoNotCare -> match_stmt (Block t_fun_.f_body) (Block p_fun_.f_body) env
1010 | _ -> LM.match_attributes attributes env
1012 and match_fun_kind
1013 (t_fun_kind : fun_kind)
1014 (p_fun_kind : fun_kind)
1015 (env : matcher_env) : (match_result * matcher_env) =
1016 if t_fun_kind = p_fun_kind
1017 then dummy_success_res, env
1018 else NoMatch, env
1020 and match_variance
1021 (t_variance : variance)
1022 (p_variance : variance)
1023 (env : matcher_env) : (match_result * matcher_env) =
1024 if t_variance = p_variance
1025 then dummy_success_res, env
1026 else NoMatch, env
1028 and match_constraint_kind
1029 (t_ck : constraint_kind)
1030 (p_ck : constraint_kind)
1031 (env : matcher_env) : (match_result * matcher_env) =
1032 if t_ck = p_ck
1033 then dummy_success_res, env
1034 else NoMatch, env
1036 and match_tparam
1037 (t_tparam : tparam)
1038 (p_tparam : tparam)
1039 (env : matcher_env) : (match_result * matcher_env) =
1040 let (t_variance, t_id, t_chopt) = t_tparam in
1041 let (p_variance, p_id, p_chopt) = p_tparam in
1042 LM.match_attributes
1043 [match_variance t_variance p_variance;
1044 match_id_res t_id p_id;
1045 LM.match_list
1046 (fun _ -> false)
1047 (LM.match_pair_fn match_constraint_kind match_hint)
1048 t_chopt
1049 p_chopt]
1052 and match_kind
1053 (t_kind : kind)
1054 (p_kind : kind)
1055 (env : matcher_env) : (match_result * matcher_env) =
1056 if t_kind = p_kind
1057 then dummy_success_res, env
1058 else NoMatch, env
1060 and match_fun_param
1061 (t_fun_param : fun_param)
1062 (p_fun_param : fun_param)
1063 (env : matcher_env) : (match_result * matcher_env) =
1064 let attributes =
1065 [match_id_res t_fun_param.param_id p_fun_param.param_id;
1066 LM.match_option
1067 match_hint
1068 t_fun_param.param_hint
1069 p_fun_param.param_hint;
1070 match_bool t_fun_param.param_is_reference p_fun_param.param_is_reference;
1071 match_bool t_fun_param.param_is_variadic p_fun_param.param_is_variadic;
1072 LM.match_option
1073 match_expr
1074 t_fun_param.param_expr
1075 p_fun_param.param_expr;
1076 LM.match_option
1077 match_kind
1078 t_fun_param.param_modifier
1079 p_fun_param.param_modifier;
1080 LM.match_list
1081 is_star_user_attribute
1082 match_user_attribute
1083 t_fun_param.param_user_attributes
1084 p_fun_param.param_user_attributes] in
1085 match get_id_type p_fun_param.param_id with
1086 | DoNotCare | Wildcard -> dummy_success_res, env
1087 | _ -> LM.match_attributes attributes env
1089 and match_class_kind
1090 (t_class_kind : class_kind)
1091 (p_class_kind : class_kind)
1092 (env : matcher_env) : (match_result * matcher_env) =
1093 if t_class_kind = p_class_kind
1094 then dummy_success_res, env
1095 else NoMatch, env
1097 and match_enum_
1098 (t_enum_ : enum_)
1099 (p_enum_ : enum_)
1100 (env : matcher_env) : (match_result * matcher_env) =
1101 LM.match_attributes
1102 [match_hint t_enum_.e_base p_enum_.e_base;
1103 LM.match_option match_hint t_enum_.e_constraint p_enum_.e_constraint]
1106 and match_class_
1107 (t_class_ : class_)
1108 (p_class_ : class_)
1109 (env0 : matcher_env) : (match_result * matcher_env) =
1110 let actions =
1111 [match_id_res t_class_.c_name p_class_.c_name;
1112 match_bool t_class_.c_final p_class_.c_final;
1113 match_bool t_class_.c_is_xhp p_class_.c_is_xhp;
1114 LM.match_list
1115 is_star_user_attribute
1116 match_user_attribute
1117 t_class_.c_user_attributes
1118 p_class_.c_user_attributes;
1119 match_class_kind t_class_.c_kind p_class_.c_kind;
1120 LM.match_list
1121 is_star_tparam
1122 match_tparam
1123 t_class_.c_tparams
1124 p_class_.c_tparams;
1125 LM.match_list
1126 is_star_hint
1127 match_hint
1128 t_class_.c_extends
1129 p_class_.c_extends;
1130 LM.match_list
1131 is_star_hint
1132 match_hint
1133 t_class_.c_implements
1134 p_class_.c_implements;
1135 match_namespace t_class_.c_namespace p_class_.c_namespace;
1136 LM.match_option match_enum_ t_class_.c_enum p_class_.c_enum;
1137 LM.match_list
1138 is_star_celt
1139 match_class_elt
1140 t_class_.c_body
1141 p_class_.c_body] in
1142 match get_id_type p_class_.c_name with
1143 | Wildcard -> dummy_success_res, env0
1144 | DoNotCare -> LM.match_list
1145 is_star_celt
1146 match_class_elt
1147 t_class_.c_body
1148 p_class_.c_body
1149 env0
1150 | _ -> LM.match_attributes actions env0
1152 and match_ca_type
1153 (t_cat : ca_type)
1154 (p_cat : ca_type)
1155 (env : matcher_env) : (match_result * matcher_env) =
1156 match t_cat, p_cat with
1157 | CA_hint t_h, CA_hint p_h ->
1158 match_hint t_h p_h env
1159 | CA_enum t_sl, CA_enum p_sl ->
1160 revert_env_if_no_match
1161 (LM.match_list (fun _ -> false) match_string t_sl p_sl env)
1163 | _, _ -> NoMatch, env
1165 and match_ca_field
1166 (t_caf : ca_field)
1167 (p_caf : ca_field)
1168 (env : matcher_env) : (match_result * matcher_env) =
1169 LM.match_attributes
1170 [match_ca_type t_caf.ca_type p_caf.ca_type;
1171 match_id_res t_caf.ca_id p_caf.ca_id;
1172 LM.match_option match_expr t_caf.ca_value p_caf.ca_value;
1173 match_bool t_caf.ca_required p_caf.ca_required]
1176 and match_typeconst
1177 (t_tc : typeconst)
1178 (p_tc : typeconst)
1179 (env : matcher_env) : (match_result * matcher_env) =
1180 LM.match_attributes
1181 [match_id_res t_tc.tconst_name p_tc.tconst_name;
1182 match_bool t_tc.tconst_abstract p_tc.tconst_abstract;
1183 LM.match_option
1184 match_hint
1185 t_tc.tconst_constraint
1186 p_tc.tconst_constraint;
1187 LM.match_option match_hint t_tc.tconst_type p_tc.tconst_type]
1190 and match_class_attr
1191 (t_class_attr : class_attr)
1192 (p_class_attr : class_attr)
1193 (env : matcher_env) : (match_result * matcher_env) =
1194 match t_class_attr, p_class_attr with
1195 | CA_name t_id, CA_name p_id ->
1196 match_id_res t_id p_id env
1197 | CA_field t_cf, CA_field p_cf ->
1198 match_ca_field t_cf p_cf env
1199 | _, _ -> NoMatch, env
1201 and match_trait_req_kind
1202 (t_trk : trait_req_kind)
1203 (p_trk : trait_req_kind)
1204 (env : matcher_env) : (match_result * matcher_env) =
1205 if t_trk = p_trk
1206 then dummy_success_res, env
1207 else NoMatch, env
1209 and match_class_var
1210 (_, t_cv_id, t_cv_expr : class_var)
1211 (_, p_cv_od, p_cv_expr : class_var)
1212 (env : matcher_env) : (match_result * matcher_env) =
1213 LM.match_attributes
1214 [match_id_res t_cv_id p_cv_od;
1215 LM.match_option match_expr t_cv_expr p_cv_expr]
1218 and match_class_elt
1219 (t_class_elt : class_elt)
1220 (p_class_elt : class_elt)
1221 (env : matcher_env) : (match_result * matcher_env) =
1222 match t_class_elt, p_class_elt with
1223 | Ast.Method t_method_, Ast.Method p_method_ ->
1224 match_method_ t_method_ p_method_ env
1225 | Const (t_hopt, t_iel), Const (p_hopt, p_iel) ->
1226 LM.match_attributes
1227 [LM.match_option match_hint t_hopt p_hopt;
1228 LM.match_list
1229 (fun _ -> false)
1230 (LM.match_pair_fn match_id_res match_expr)
1231 t_iel
1232 p_iel]
1234 | AbsConst (t_hopt, t_id), AbsConst (p_hopt, p_id) ->
1235 LM.match_attributes
1236 [LM.match_option match_hint t_hopt p_hopt;
1237 match_id_res t_id p_id]
1239 | Attributes t_cal, Attributes p_cal ->
1240 revert_env_if_no_match
1241 (LM.match_list (fun _ -> false) match_class_attr t_cal p_cal env)
1243 | TypeConst t_tc, TypeConst p_tc ->
1244 match_typeconst t_tc p_tc env
1245 | ClassUse t_h, ClassUse p_h
1246 | XhpAttrUse t_h, XhpAttrUse p_h ->
1247 match_hint t_h p_h env
1248 | ClassTraitRequire (t_trk, t_h), ClassTraitRequire (p_trk, p_h) ->
1249 LM.match_attributes
1250 [match_trait_req_kind t_trk p_trk;
1251 match_hint t_h p_h]
1253 | ClassVars (t_kl, t_hopt, t_cvl), ClassVars (p_kl, p_hopt, p_cvl) ->
1254 LM.match_attributes
1255 [LM.match_list (fun _ -> false) match_kind t_kl p_kl;
1256 LM.match_option match_hint t_hopt p_hopt;
1257 LM.match_list is_star_class_var match_class_var t_cvl p_cvl]
1259 | XhpAttr (t_hopt, t_cv, t_b, t_pelopt),
1260 XhpAttr (p_hopt, p_cv, p_b, p_pelopt) ->
1261 LM.match_attributes
1262 [LM.match_option match_hint t_hopt p_hopt;
1263 match_bool t_b p_b;
1264 match_class_var t_cv p_cv;
1265 LM.match_option
1266 (LM.match_pair_fn
1267 (* we don't care about the position *)
1268 (fun _ _ env -> dummy_success_res, env)
1269 (* want to match the expr list correctly*)
1270 match_expr_list)
1271 t_pelopt
1272 p_pelopt]
1274 | _, _ -> (NoMatch, env)
1276 and match_method_
1277 (t_method_ : method_)
1278 (p_method_ : method_)
1279 (env : matcher_env) : (match_result * matcher_env) =
1280 let attributes =
1281 [match_id_res t_method_.m_name p_method_.m_name;
1282 LM.match_list
1283 (fun _ -> false)
1284 match_kind
1285 t_method_.m_kind
1286 p_method_.m_kind;
1287 LM.match_list
1288 (fun _ -> false)
1289 match_tparam
1290 t_method_.m_tparams
1291 p_method_.m_tparams;
1292 LM.match_list
1293 is_star_fun_param
1294 match_fun_param
1295 t_method_.m_params
1296 p_method_.m_params;
1297 LM.match_option match_hint t_method_.m_ret p_method_.m_ret;
1298 match_bool t_method_.m_ret_by_ref p_method_.m_ret_by_ref;
1299 LM.match_list
1300 is_star_user_attribute
1301 match_user_attribute
1302 t_method_.m_user_attributes
1303 p_method_.m_user_attributes;
1304 match_fun_kind t_method_.m_fun_kind p_method_.m_fun_kind;
1305 match_stmt
1306 (Block t_method_.m_body)
1307 (Block p_method_.m_body)] in
1308 match get_id_type p_method_.m_name with
1309 | Wildcard -> dummy_success_res, env
1310 | DoNotCare -> match_stmt
1311 (Block t_method_.m_body)
1312 (Block p_method_.m_body)
1314 | _ -> LM.match_attributes attributes env
1316 and match_case
1317 (t_case : case)
1318 (p_case : case)
1319 (env : matcher_env) : (match_result * matcher_env) =
1320 (* maybe have TODO something to make sure wildcarding works correctly here *)
1321 match t_case, p_case with
1322 | Default t_b, Default p_b ->
1323 match_stmt (Block t_b) (Block p_b) env
1324 | Case (t_e, t_b), Case (p_e, p_b) ->
1325 LM.match_attributes
1326 [match_expr t_e p_e;
1327 match_stmt (Block t_b) (Block p_b)]
1329 | _, _ -> NoMatch, env
1331 and match_as_expr
1332 (t_as_expr : as_expr)
1333 (p_as_expr : as_expr)
1334 (env : matcher_env) : (match_result * matcher_env) =
1335 match t_as_expr, p_as_expr with
1336 | As_v t_e, As_v p_e ->
1337 match_expr t_e p_e env
1338 | As_kv (t_e1, t_e2), As_kv (p_e1, p_e2) ->
1339 LM.match_attributes
1340 [match_expr t_e1 p_e1;
1341 match_expr t_e2 p_e2]
1343 | _, _ -> NoMatch, env
1345 and match_catch
1346 (t_catch : catch)
1347 (p_catch : catch)
1348 (env : matcher_env) : (match_result * matcher_env) =
1349 let (t_i1, t_i2, t_b) = t_catch in
1350 let (p_i1, p_i2, p_b) = p_catch in
1351 LM.match_attributes
1352 [match_id_res t_i1 p_i1;
1353 match_id_res t_i2 p_i2;
1354 match_stmt (Block t_b) (Block p_b)]
1357 (* def is KStar iff it is a class, or function or string literal with
1358 name "__KSTAR" *)
1359 and is_star_def (p_def : def) : bool =
1360 match p_def with
1361 | Class p_class -> (get_id_type p_class.c_name) = KStar
1362 | Fun p_fun_ -> (get_id_type p_fun_.f_name) = KStar
1363 | Ast.Stmt p_stmt -> is_star_stmt p_stmt
1364 | _ -> false
1366 (* TODO implement this correctly *)
1367 and is_star_hint (_p_hint : hint) : bool = false
1369 (* tparams cannot be KStar matched but this function is in case that changes *)
1370 and is_star_tparam (_p_tparam : tparam) : bool = false
1372 (* fun_params cannot be KStar matched but this function is in case
1373 that changes *)
1374 and is_star_fun_param (_p_fun_param : fun_param) : bool = false
1376 (* user_attributes cannot be KStar matched but this function
1377 allows us to change that later *)
1378 and is_star_user_attribute (_p_uatt : user_attribute) : bool = false
1380 (* doesn't make sense to star match a list of class variables *)
1381 and is_star_class_var (_p_cvar : class_var) : bool = false
1383 (* class_elt is KStar iff it is a method with name "__KSTAR" *)
1384 and is_star_celt (celt : class_elt) : bool =
1385 match celt with
1386 | Ast.Method meth ->
1387 let id_type = (get_id_type meth.m_name) in
1388 id_type = KStar
1389 | _ -> false
1391 (* stmt is KStar type iff it is the string literal "__KSTAR" *)
1392 and is_star_stmt
1393 (p_stmt : stmt) : bool =
1394 match p_stmt with
1395 | Ast.Expr (_,(String pstr)) -> get_id_type pstr = KStar
1396 | _ -> false
1398 (* stmt is KStar type iff it is the string literal "__KSTAR" *)
1399 and is_star_expr (p_expr : expr) : bool =
1400 match snd p_expr with
1401 | String pstr -> get_id_type pstr = KStar
1402 | _ -> false
1404 (* catch is KStar type iff the name of the variable the exception is bound
1405 to is "__KSTAR"
1406 e.g. catch (Exception $__KSTAR *)
1407 and is_star_catch (p_catch : catch) : bool =
1408 let (_,ident,_) = p_catch in
1409 get_id_type ident = KStar
1411 (* case is type KStar iff if the expression to match is "__KSTAR"
1412 e.g. case "__KSTAR": *)
1413 and is_star_case (p_case : case) : bool =
1414 match p_case with
1415 | Case (exp,_) -> is_star_expr exp
1416 | Default _ -> false
1418 (* is KStar type iff the value is "__KSTAR" *)
1419 and is_star_afield (afl : afield) : bool =
1420 match afl with
1421 | AFvalue exp -> is_star_expr exp
1422 | AFkvalue (exp,_) -> is_star_expr exp
1424 (* stmt is a wildcard iff it consists of just a wildcard expr:
1425 "__ANY"; *)
1426 and is_wildcard_stmt (p_stm : stmt) : bool =
1427 match p_stm with
1428 | Ast.Expr (_,(String pstr)) -> (get_id_type pstr) = Wildcard
1429 | _ -> false
1431 (* handle_sa_hd_fn for stmt list for SkipAny matching, see comment on
1432 match_list_with_skips *)
1433 and act_if_skip_any
1434 (try_match_list_fn :
1435 stmt list -> stmt list -> stmt list -> matcher_env ->
1436 match_result * matcher_env)
1437 (p_hd : stmt)
1438 (t_tl : stmt list)
1439 (p_tl : stmt list)
1440 (skipped_text : stmt list)
1441 (env : matcher_env) : (match_result * matcher_env) option =
1442 if is_skip_any_stmt p_hd
1443 then Some
1444 (revert_env_if_no_match
1445 (try_match try_match_list_fn t_tl (p_hd :: p_tl) skipped_text env)
1446 env)
1447 else None
1449 (* Tries to match the text with the pattern recursively, sensitive to KStars
1450 and SkipAnys
1451 handle_sa_fn for stmt list SkipAny matching,
1452 see comment on match_list_with_skips*)
1453 and try_match
1454 (try_match_list_fn :
1455 stmt list -> stmt list -> stmt list -> matcher_env ->
1456 match_result * matcher_env)
1457 (t_list : stmt list)
1458 (p_list : stmt list)
1459 (skipped_text : stmt list)
1460 (env : matcher_env) :
1461 match_result * matcher_env =
1462 (* given a chunk of text that was skipped over by a SkipAny
1463 find all the possible chunks that could match the SkipAny pattern
1464 (all child blocks + self) *)
1465 let find_child_text (text_chunk : stmt list) : block list =
1466 let visitor = new block_finding_visitor () in
1467 visitor#on_block [] text_chunk in
1469 (* given an output of find_child_text, find all the matches,
1470 update env if necessary *)
1471 let match_child_text
1472 (child_text : block list)
1473 (pattern : stmt list)
1474 (env : matcher_env) : match_result * matcher_env =
1475 List.fold_left
1476 ~f:(fun (res_so_far, env) text_blk ->
1477 let text_res, env' =
1478 match_stmt (Block text_blk) (Block pattern) env in
1479 (concat_match_results_nodup [res_so_far; text_res]), env')
1480 ~init:(NoMatch, env)
1481 child_text in
1483 (* Try to match a SkipAny: match sa_pattern over all children of
1484 skipped_text, as well as making sure the rest of the pattern and
1485 text match *)
1486 let match_single_skipany t_list p_list skipped_text sa_pattern env =
1487 (* Find all blocks that are children of the relevant
1488 part of the text.
1489 NOTE skipped_text is reversed because of the way we appended to it, so
1490 we need to reverse it before finding children *)
1491 let sa_text = find_child_text (List.rev skipped_text) in
1492 (* look for matches over these child blocks *)
1493 let cur_res, env' = match_child_text sa_text sa_pattern env in
1494 (* check to see if remaining text matches remaining pattern (note these
1495 must be done in order so that the environment is correct - consistent
1496 with a linear pass through the file) *)
1497 let later_res, env2 = try_match try_match_list_fn t_list p_list [] env' in
1498 match cur_res, later_res with
1499 | NoMatch, NoMatch | Matches _, NoMatch
1500 | NoMatch, Matches _ -> NoMatch, env
1501 (* if both matches succeeded, we have a match *)
1502 | Matches _, Matches _ ->
1503 concat_match_results_nodup [cur_res; later_res],
1504 env2 in
1506 (* Handle the case where the pattern started with a SkipAny, figuring
1507 out all possible amounts of text that the SkipAny could match over *)
1508 let rec handle_leading_skipany t_list p_list skipped_text sa_pattern env =
1509 match t_list with
1510 (* no text left, try matching pattern with the skipped text *)
1511 | [] -> begin
1512 match skipped_text with
1513 | [] ->
1514 NoMatch, env
1515 | _ ->
1516 match_single_skipany [] p_list skipped_text sa_pattern env end
1517 | t_hd :: t_tl ->
1518 (* Try skipping more of the text *)
1519 let rec_res, rec_env =
1520 handle_leading_skipany
1521 t_tl p_list (t_hd :: skipped_text) sa_pattern env in
1522 (* Try matching the SkipAny with the current amount of skipped text *)
1523 let norec_res, norec_env =
1524 match_single_skipany t_list p_list skipped_text sa_pattern env in
1525 (* if either of those worked, we return a success *)
1526 match rec_res, norec_res with
1527 | NoMatch, NoMatch -> NoMatch, env
1528 | Matches _, NoMatch -> rec_res, rec_env
1529 | NoMatch, Matches _ -> norec_res, norec_env
1530 | Matches _, Matches _ ->
1531 concat_match_results_nodup [rec_res; norec_res],
1532 (merge_envs rec_env norec_env) in
1534 (* preprocess a SkipAny token from the pattern if we find one to find
1535 the pattern that will be matched later with the text it skips *)
1536 match p_list with
1537 | hd :: tl when is_skip_any_unproc hd ->begin
1538 (* next block is the pattern we want to recursively match*)
1539 let skipany_body, after_skipany =
1540 match tl with
1541 | hd :: tail ->
1542 let body =
1543 match hd with
1544 | Block bl -> star_stmt :: bl @ [star_stmt]
1545 (* incorrectly formatted pattern - SkipAny not followed by
1546 block representing pattern to match *)
1547 | _ -> failwith "missing block after skip stmt" in
1548 body, tail
1549 (* incorrectly formatted pattern - SkipAny not followed by block
1550 representing pattern to match *)
1551 | _ -> failwith "missing block after skip stmt" in
1552 (* Deal with the leading SkipAny *)
1553 let p_list = star_stmt :: after_skipany in
1554 handle_leading_skipany t_list p_list skipped_text skipany_body env
1556 (* If there wasn't a leading SkipAny, don't do anything, just call the
1557 try_match_list_fn *)
1558 | _ ->
1559 revert_env_if_no_match
1560 (try_match_list_fn t_list p_list skipped_text env) env
1562 (* matches stmts by recursively matching their subcomponents *)
1563 and match_stmt
1564 (t_stmt : stmt)
1565 (p_stmt : stmt)
1566 (env0 : matcher_env) : (match_result * matcher_env) =
1567 let env =
1568 add_mvar_node env0 is_meta_stmt p_stmt (Hh_match_utils.Stmt t_stmt) in
1569 let p_stmt = instantiate_mvar_stmt env p_stmt in
1570 let old_env = env in
1571 (if is_wildcard_stmt p_stmt
1572 then
1573 match t_stmt with
1574 | Noop -> NoMatch, env
1575 | _ -> (dummy_success_res, env)
1576 else
1577 match t_stmt, p_stmt with
1578 | Unsafe, Unsafe ->
1579 (dummy_success_res, env)
1580 | Fallthrough, Fallthrough ->
1581 (dummy_success_res, env)
1582 | Ast.Expr t_expr, Ast.Expr p_expr ->
1583 match_expr t_expr p_expr env
1584 | Block t_sl, Block p_sl ->
1585 LM.match_list_with_skips
1586 is_star_stmt
1587 (Some is_meta_stmt)
1588 (Some (fun stm -> Hh_match_utils.Stmt stm))
1589 match_stmt
1590 try_match
1591 act_if_skip_any
1592 t_sl
1593 p_sl
1595 | Break loc, Break _
1596 | Continue loc, Continue _ ->
1597 (* use start because we want line number of beginning of construct *)
1598 (Matches [(DummyNode, Pos.pos_start loc)], env)
1599 | Throw t_e, Throw p_e ->
1600 match_expr t_e p_e env
1601 | Return (loc, t_eopt), Return (_, p_eopt) ->
1602 let opt_res = LM.match_option match_expr t_eopt p_eopt env in
1603 (* if it matched a return where the expression does not have a
1604 position *)
1605 if fst opt_res == dummy_success_res
1606 then (Matches [(DummyNode, Pos.pos_start loc)], env)
1607 else opt_res
1608 | Static_var t_el, Static_var p_el ->
1609 match_expr_list t_el p_el env
1610 | If (t_e, t_b1, t_b2), If (p_e, p_b1, p_b2) ->
1611 LM.match_attributes
1612 [match_expr t_e p_e;
1613 match_stmt (Block t_b1) (Block p_b1);
1614 match_stmt (Block t_b2) (Block p_b2)]
1616 | Do (t_b, t_e), Do (p_b, p_e) ->
1617 LM.match_attributes
1618 [match_stmt (Block t_b) (Block p_b);
1619 match_expr t_e p_e]
1621 | While (t_e, t_b) , While (p_e, p_b) ->
1622 LM.match_attributes
1623 [match_expr t_e p_e;
1624 match_stmt (Block t_b) (Block p_b)]
1626 | For (t_e1, t_e2, t_e3, t_b), For (p_e1, p_e2, p_e3, p_b) ->
1627 LM.match_attributes
1628 [match_expr t_e1 p_e1;
1629 match_expr t_e2 p_e2;
1630 match_expr t_e3 p_e3;
1631 match_stmt (Block t_b) (Block p_b)]
1633 | Switch (t_e, t_cl), Switch (p_e, p_cl) ->
1634 LM.match_attributes
1635 [match_expr t_e p_e;
1636 LM.match_list is_star_case match_case t_cl p_cl]
1638 | Foreach (t_e, t_aopt, t_ase, t_b), Foreach (p_e, p_aopt, p_ase, p_b) ->
1639 let match_aopt t_aopt p_aopt env =
1640 match t_aopt, p_aopt with
1641 | None, Some _ | Some _, None -> NoMatch, env
1642 | None, None | Some _, Some _ -> dummy_success_res, env in
1643 LM.match_attributes
1644 [match_expr t_e p_e;
1645 match_aopt t_aopt p_aopt;
1646 match_as_expr t_ase p_ase;
1647 match_stmt (Block t_b) (Block p_b)]
1649 | Try (t_b1, t_cl, t_b2), Try (p_b1, p_cl, p_b2) ->
1650 LM.match_attributes
1651 [match_stmt (Block t_b1) (Block p_b1);
1652 LM.match_list is_star_catch match_catch t_cl p_cl;
1653 match_stmt (Block t_b2) (Block p_b2)]
1655 | Noop, Noop->
1656 (dummy_success_res, env)
1657 | _, _ -> (NoMatch, old_env))
1658 |> LM.patch_if_necc
1659 t_stmt p_stmt adjust_range_stmt
1660 env.transformations.stmt_delete_list
1661 env.transformations.stmt_transf_map
1662 stmt_to_text
1663 (Ast_code_extent.source_extent_stmt env.file env.source)
1664 |> function ret -> revert_env_if_no_match ret env0
1666 and is_wildcard_expr (exp : expr_) : bool =
1667 match exp with
1668 | String pstr -> (get_id_type pstr) = Wildcard
1669 | _ -> false
1671 and match_afield
1672 (t_afl : afield)
1673 (p_afl : afield)
1674 (env : matcher_env) : (match_result * matcher_env) =
1675 match t_afl, p_afl with
1676 | AFvalue t_e, AFvalue p_e -> match_expr t_e p_e env
1677 | AFkvalue (t_e1, t_e2), AFkvalue (p_e1, p_e2) ->
1678 LM.match_attributes
1679 [match_expr t_e1 p_e1;
1680 match_expr t_e2 p_e2]
1682 | _, _ -> (NoMatch, env)
1684 and match_shape_field_name
1685 (t_sfn : shape_field_name)
1686 (p_sfn : shape_field_name)
1687 (env : matcher_env) : (match_result * matcher_env) =
1688 let old_env = env in
1689 match t_sfn, p_sfn with
1690 | SFlit t_pstr, SFlit p_pstr ->
1691 revert_env_if_no_match (match_id_res t_pstr p_pstr env) old_env
1692 | SFclass_const (t_i, t_pstr), SFclass_const (p_i, p_pstr) ->
1693 LM.match_attributes
1694 [match_id_res t_i p_i;
1695 match_id_res t_pstr p_pstr]
1697 | _, _ -> (NoMatch, old_env)
1699 and match_og_null_flavor
1700 (t_onf : og_null_flavor)
1701 (p_onf : og_null_flavor)
1702 (env : matcher_env) : (match_result * matcher_env) =
1703 if t_onf = p_onf
1704 then dummy_success_res, env
1705 else NoMatch, env
1707 and match_uop
1708 (t_uop : uop)
1709 (p_uop : uop)
1710 (env : matcher_env) : (match_result * matcher_env) =
1711 if t_uop = p_uop
1712 then dummy_success_res, env
1713 else NoMatch, env
1715 and match_bop
1716 (t_bop : bop)
1717 (p_bop : bop)
1718 (env : matcher_env) : (match_result * matcher_env) =
1719 match t_bop, p_bop with
1720 | Eq t_bopt, Eq p_bopt ->
1721 LM.match_option match_bop t_bopt p_bopt env
1722 | _, _ ->
1723 if t_bop = p_bop
1724 then dummy_success_res, env
1725 else NoMatch, env
1727 and match_import_flavor
1728 (t_if : import_flavor)
1729 (p_if : import_flavor)
1730 (env : matcher_env) : (match_result * matcher_env) =
1731 if t_if = p_if
1732 then dummy_success_res, env
1733 else NoMatch, env
1735 (* Tries to match the text with the pattern recursively, trying to match
1736 the given pattern in the program specified *)
1737 and handle_skipany
1738 (text : program)
1739 (pat : skipany_ast_node)
1740 (env : matcher_env) : match_result * matcher_env =
1741 (* given a chunk of text that was skipped over by a SkipAny
1742 find all the possible chunks that could match the SkipAny pattern
1743 (all child blocks + self) *)
1744 let find_child_text visitor =
1745 visitor#on_program [] text in
1747 (* given an output of find_child_text, find all the matches,
1748 update env if necessary *)
1749 let match_child_text
1750 (child_text)
1751 (pat)
1752 (match_fn)
1753 (env : matcher_env) : match_result * matcher_env =
1754 List.fold_left
1755 ~f:(fun (res_so_far, env) text_node ->
1756 let text_res, env' = match_fn text_node pat env in
1757 (concat_match_results_nodup [res_so_far; text_res]), env')
1758 ~init:(NoMatch, env)
1759 child_text in
1761 match pat with
1762 | SkipanyExpr e -> match_child_text
1763 (find_child_text (new expr_finding_visitor ())) e match_expr env
1764 | SkipanyBlock b -> match_child_text
1765 (find_child_text (new block_finding_visitor ())) b
1766 match_statements_in_block env
1768 and handle_expr_skipany
1769 (text : program)
1770 (p_expr : expr)
1771 (env : matcher_env) : match_result * matcher_env =
1772 handle_skipany text (SkipanyExpr p_expr) env
1774 and handle_stmt_skipany
1775 (text : program)
1776 (p_stmts : block)
1777 (env : matcher_env) : match_result * matcher_env =
1778 handle_skipany text (SkipanyBlock p_stmts) env
1780 (* Finds all matches of the statement list p_stmts inside the block t_block *)
1781 and match_statements_in_block
1782 (t_block : block)
1783 (p_stmts : stmt list)
1784 (env : matcher_env) : (match_result * matcher_env) =
1785 let p_len = List.length p_stmts in
1787 (* Walk through a list of statements and check at each one if the sequence of
1788 statements starting there matches the pattern *)
1789 let rec find_matching_sublists t_stmts env : match_result list * matcher_env =
1790 match t_stmts with
1791 | [] -> ([], env)
1792 | _::t_stmts' as stmts ->
1793 let t_stmts = List.take stmts p_len in
1794 (* Produces an option on a list of matched statements and the matching
1795 environment. If there is one statement that isn't matched by the
1796 pattern this will be None *)
1797 let rec match_stmt_lists t_stmts p_stmts env =
1798 match (t_stmts, p_stmts) with
1799 | ([], []) -> Some ([], env)
1800 | (ts::t_stmts', ps::p_stmts') -> begin
1801 match match_stmt ts ps env with
1802 | (NoMatch, _) -> None
1803 | (m, env') -> begin
1804 match match_stmt_lists t_stmts' p_stmts' env' with
1805 | None -> None
1806 | Some (l, env'') -> Some (m :: l, env'') end
1808 | _ -> None in
1810 let (matches, env') = match match_stmt_lists t_stmts p_stmts env with
1811 | None -> ([], env)
1812 | Some p -> p in
1814 (* Collapse match results into one match, and recurse *)
1815 let (l, e) = find_matching_sublists t_stmts' env' in
1816 (concat_match_results_nodup matches :: l, e) in
1818 let matches, env' = find_matching_sublists t_block env in
1819 (concat_match_results_nodup matches, env')
1821 and match_expr
1822 (t_expr : expr)
1823 (p_expr : expr)
1824 (env0 : matcher_env) : (match_result * matcher_env) =
1825 (* remember do revert *)
1826 let env =
1827 add_mvar_node env0 is_meta_expr p_expr (Hh_match_utils.Expr t_expr) in
1828 let p_expr = instantiate_mvar_expr env p_expr in
1829 let (t_pos, t_expr_) = t_expr in
1830 let (_, p_expr_) = p_expr in
1831 let success_res =
1832 Matches [(Hh_match_utils.Expr t_expr, Pos.pos_start t_pos)] in
1833 (* if pattern is a wildcard we will always match*)
1834 (if is_wildcard_expr p_expr_
1835 then (success_res, env)
1836 else
1837 (* if the match was successful, we stick on a result corresponding to the
1838 expr as a whole. *)
1839 let exp_res =
1840 match t_expr_, p_expr_ with
1841 | Array t_afl, Array p_afl ->
1842 LM.match_list is_star_afield match_afield t_afl p_afl env
1843 | Shape t_sfnel, Shape p_sfnel ->
1844 LM.match_list
1845 (fun _ -> false)
1846 (LM.match_pair_fn match_shape_field_name match_expr)
1847 t_sfnel
1848 p_sfnel
1850 | Collection (t_i, t_al), Collection (p_i, p_al) ->
1851 LM.match_attributes
1852 [match_id_res t_i p_i;
1853 LM.match_list
1854 is_star_afield
1855 match_afield
1856 t_al
1857 p_al]
1859 | Null, Null
1860 | True, True
1861 | False, False
1862 | Yield_break, Yield_break ->
1863 dummy_success_res, env
1864 | Id_type_arguments (t_id, t_hl), Id_type_arguments (p_id, p_hl) ->
1865 LM.match_attributes
1866 [match_id_res t_id p_id;
1867 LM.match_list (fun _ -> false) match_hint t_hl p_hl]
1869 | Id t_id, Id p_id
1870 | Lvar t_id, Lvar p_id ->
1871 match_id_res t_id p_id env
1872 | Obj_get (t_e1, t_e2, t_onf), Obj_get (p_e1, p_e2, p_onf) ->
1873 LM.match_attributes
1874 [match_expr t_e1 p_e1;
1875 match_expr t_e2 p_e2;
1876 match_og_null_flavor t_onf p_onf]
1878 | Array_get (t_e, t_eopt), Array_get (p_e, p_eopt) ->
1879 LM.match_attributes
1880 [match_expr t_e p_e;
1881 LM.match_option match_expr t_eopt p_eopt]
1883 | Class_get (t_id, t_pstr), Class_get (p_id, p_pstr)
1884 | Class_const (t_id, t_pstr), Class_const (p_id, p_pstr) ->
1885 LM.match_attributes
1886 [match_id_res t_id p_id;
1887 match_id_res t_pstr p_pstr]
1889 | Call (t_e, t_el1, t_el2), Call (p_e, p_el1, p_el2) ->
1890 LM.match_attributes
1891 [match_expr t_e p_e;
1892 match_expr_list t_el1 p_el1;
1893 match_expr_list t_el2 p_el2]
1895 | New (t_e, t_el1, t_el2), New (p_e, p_el1, p_el2) ->
1896 LM.match_attributes
1897 [match_expr t_e p_e;
1898 match_expr_list t_el1 p_el1;
1899 match_expr_list t_el2 p_el2]
1901 | Int t_pstr, Int p_pstr
1902 | Float t_pstr, Float p_pstr
1903 | String t_pstr, String p_pstr ->
1904 revert_env_if_no_match (match_id_res t_pstr p_pstr env) env
1905 | String2 t_el, String2 p_el ->
1906 LM.match_attributes
1907 [match_expr_list t_el p_el]
1909 | Yield t_af, Yield p_af ->
1910 revert_env_if_no_match (match_afield t_af p_af env) env
1911 | Clone t_e, Clone p_e
1912 | Await t_e, Await p_e
1913 | Unsafeexpr t_e, Unsafeexpr p_e ->
1914 revert_env_if_no_match (match_expr t_e p_e env) env
1915 | List t_el, List p_el
1916 | Expr_list t_el, Expr_list p_el->
1917 match_expr_list t_el p_el env
1918 | Cast (t_h, t_e), Cast (p_h, p_e) ->
1919 LM.match_attributes
1920 [match_hint t_h p_h;
1921 match_expr t_e p_e]
1923 | Unop (t_uop, t_e), Unop (p_uop, p_e) ->
1924 LM.match_attributes
1925 [match_uop t_uop p_uop;
1926 match_expr t_e p_e]
1928 | Binop (t_bop, t_e1, t_e2), Binop (p_bop, p_e1, p_e2) ->
1929 LM.match_attributes
1930 [match_bop t_bop p_bop;
1931 match_expr t_e1 p_e1;
1932 match_expr t_e2 p_e2]
1934 | Eif (t_e1, t_eopt, t_e2), Eif (p_e1, p_eopt, p_e2) ->
1935 LM.match_attributes
1936 [match_expr t_e1 p_e1;
1937 LM.match_option match_expr t_eopt p_eopt;
1938 match_expr t_e2 p_e2]
1940 | InstanceOf (t_e1, t_e2), InstanceOf (p_e1, p_e2) ->
1941 LM.match_attributes
1942 [match_expr t_e1 p_e1;
1943 match_expr t_e2 p_e2]
1945 | Efun (t_f, t_ibl), Efun (p_f, p_ibl) ->
1946 LM.match_attributes
1947 [match_fun_ t_f p_f;
1948 LM.match_list
1949 (fun _ -> false)
1950 (LM.match_pair_fn match_id_res match_bool)
1951 t_ibl
1952 p_ibl]
1954 | Lfun t_f, Lfun p_f ->
1955 revert_env_if_no_match (match_fun_ t_f p_f env) env
1956 | Xml (t_i, t_iel, t_el), Xml (p_i, p_iel, p_el) ->
1957 (*TODO make XML matching order-insensitive*)
1958 LM.match_attributes
1959 [match_id_res t_i p_i;
1960 LM.match_list
1961 (fun _ -> false)
1962 (LM.match_pair_fn match_id_res match_expr)
1963 t_iel
1964 p_iel;
1965 match_expr_list t_el p_el]
1967 | Import (t_if, t_e), Import (p_if, p_e) ->
1968 LM.match_attributes
1969 [match_import_flavor t_if p_if;
1970 match_expr t_e p_e]
1972 | _, _ -> (NoMatch, env) in
1973 (match fst exp_res with
1974 | NoMatch -> exp_res
1975 | Matches _ ->
1976 concat_match_results [(fst exp_res); success_res], snd exp_res))
1977 |> LM.patch_if_necc
1978 t_expr p_expr adjust_range_expr
1979 env.transformations.expr_delete_list
1980 env.transformations.expr_transf_map
1981 expr_to_text
1982 (Ast_code_extent.source_extent_expr env.file env.source)
1983 |> function ret -> revert_env_if_no_match ret env0
1985 and match_shape_field
1986 (t_sf : shape_field)
1987 (p_sf : shape_field)
1988 (env : matcher_env) : (match_result * matcher_env) =
1989 match t_sf, p_sf with
1990 | {sf_optional=t_sf_optional; sf_name=t_sfn; sf_hint=t_sfh},
1991 {sf_optional=p_sf_optional; sf_name=p_sfn; sf_hint=p_sfh}
1992 when t_sf_optional = p_sf_optional ->
1993 let t_sf_pair = (t_sfn, t_sfh) in
1994 let p_sf_pair = (p_sfn, p_sfh) in
1995 (LM.match_pair_fn match_shape_field_name match_hint)
1996 t_sf_pair
1997 p_sf_pair
1999 | _ -> NoMatch, env
2001 and match_hint
2002 (t_hint : hint)
2003 (p_hint : hint)
2004 (env : matcher_env) : (match_result * matcher_env) =
2005 let child_res =
2006 match (snd t_hint), (snd p_hint) with
2007 | Hoption t_hint, Hoption p_hint ->
2008 match_hint t_hint p_hint env
2009 | Hfun (t_hl, t_b, t_h), Hfun (p_hl, p_b, p_h) ->
2010 LM.match_attributes
2011 [LM.match_list (fun _ -> false) match_hint t_hl p_hl;
2012 match_bool t_b p_b;
2013 match_hint t_h p_h]
2015 | Htuple t_hl, Htuple p_hl ->
2016 LM.match_list (fun _ -> false) match_hint t_hl p_hl env
2017 | Happly (t_id, t_hl), Happly (p_id, p_hl) ->
2018 LM.match_attributes
2019 [match_id_res t_id p_id;
2020 LM.match_list (fun _ -> false) match_hint t_hl p_hl]
2022 | Hshape t_sfl, Hshape p_sfl ->
2023 LM.match_list (fun _ -> false) match_shape_field t_sfl p_sfl env
2024 | Haccess (t_id1, t_id2, t_idl), Haccess (p_id1, p_id2, p_idl) ->
2025 LM.match_attributes
2026 [match_id_res t_id1 p_id1;
2027 match_id_res t_id2 p_id2;
2028 LM.match_list (fun _ -> false) match_id_res t_idl p_idl]
2030 | _, _ -> NoMatch, env in
2031 match fst child_res with
2032 | NoMatch -> NoMatch, env
2033 | Matches _ ->
2034 (update_res_with
2035 (fst child_res) (Hint t_hint) (Pos.pos_start (fst t_hint))),
2036 snd child_res
2038 (* to help with printing, will also remove any
2039 dummy_success_res elements *)
2040 let sort_and_remove_duplicates compare l =
2041 let sl = List.sort compare l in
2042 let rec go l acc = match l with
2043 | [] -> List.rev acc
2044 | (x::xs) when x = (DummyNode, File_pos.dummy) -> go xs acc
2045 | [x] -> List.rev (x::acc)
2046 | (x1::x2::xs) ->
2047 if File_pos.line (snd x1) = File_pos.line (snd x2)
2048 then go (x2::xs) acc
2049 else go (x2::xs) (x1::acc)
2050 in go sl []
2052 (* Actual function for finding matching nodes *)
2053 let find_matches
2054 (text : program)
2055 (text_file : Relative_path.t)
2056 (text_content : string)
2057 (pattern_parsed : Parser_hack.parser_return)
2058 : (ast_node * File_pos.t) list =
2059 let match_res =
2060 match_ast_nodes
2061 (Program text)
2062 (Program pattern_parsed.Parser_hack.ast)
2063 { file = text_file;
2064 source = text_content;
2065 uses_regexp = false;
2066 comments = pattern_parsed.Parser_hack.comments;
2067 metavars = MetavarMap.empty;
2068 transformations =
2069 { stmt_delete_list = [];
2070 expr_delete_list = [];
2071 stmt_transf_map = [];
2072 expr_transf_map = [] };
2073 patches = PatchSet.empty } in
2074 match fst match_res with
2075 | Matches result -> result
2076 | NoMatch -> []
2078 (* takes a program that consists of a single toplevel block i.e. a pattern used
2079 with the -s switch, and returns a list of that blocks' statements *)
2080 let get_skipany_stmt = function
2081 | [Ast.Stmt (Ast.Block stmts)] -> Some stmts
2082 | _ -> None
2084 let find_matches_expr_or_stmt
2085 (text : program)
2086 (text_file : Relative_path.t)
2087 (text_content : string)
2088 (pattern_parsed : Parser_hack.parser_return)
2089 (skipany_fn)
2090 (skipany_handler_fn) : (ast_node * File_pos.t) list =
2091 match skipany_fn pattern_parsed.Parser_hack.ast with
2092 | Some stmts -> begin
2093 let res, _ =
2094 skipany_handler_fn text stmts
2095 { file = text_file;
2096 source = text_content;
2097 uses_regexp = false;
2098 comments = pattern_parsed.Parser_hack.comments;
2099 metavars = MetavarMap.empty;
2100 transformations =
2101 { stmt_delete_list = [];
2102 expr_delete_list = [];
2103 stmt_transf_map = [];
2104 expr_transf_map = [] };
2105 patches = PatchSet.empty } in
2106 match res with
2107 | Matches result -> result
2108 | NoMatch -> [] end
2109 | None -> []
2111 (* function for searching for statements *)
2112 let find_matches_stmt
2113 (text : program)
2114 (text_file : Relative_path.t)
2115 (text_content : string)
2116 (pattern_parsed : Parser_hack.parser_return)
2117 : (ast_node * File_pos.t) list =
2118 find_matches_expr_or_stmt text text_file text_content pattern_parsed
2119 get_skipany_stmt handle_stmt_skipany
2121 (* Gets the expression that is the pattern while verifying the pattern
2122 is of the correct form (returning None if the pattern is not) *)
2123 let get_skipany_expr = function
2124 | [Ast.Stmt (Ast.Expr exp)] -> Some exp
2125 | _ -> None
2127 (* function for searching for expressions *)
2128 let find_matches_expr
2129 (text : program)
2130 (text_file : Relative_path.t)
2131 (text_content : string)
2132 (pattern_parsed : Parser_hack.parser_return)
2133 : (ast_node * File_pos.t) list =
2134 find_matches_expr_or_stmt text text_file text_content pattern_parsed
2135 get_skipany_expr handle_expr_skipany
2137 (* general patching function for statements and expressions *)
2138 let patch_expr_or_stmt
2139 (text : program)
2140 (text_file : Relative_path.t)
2141 (text_content : string)
2142 (pattern_parsed : Parser_hack.parser_return)
2143 (transformations : patch_maps)
2144 ~(use_hh_format : bool)
2145 (patch_fn) (skipany_fn): string option =
2146 match skipany_fn pattern_parsed.Parser_hack.ast with
2147 | Some pat -> begin
2148 let res, env =
2149 patch_fn
2150 text
2152 { file = text_file;
2153 source = text_content;
2154 uses_regexp = false;
2155 comments = pattern_parsed.Parser_hack.comments;
2156 metavars = MetavarMap.empty;
2157 transformations;
2158 patches = PatchSet.empty } in
2159 match res with
2160 | NoMatch -> None
2161 | Matches _ ->
2162 if PatchSet.is_empty env.patches
2163 then None
2164 else
2165 Some (Patcher.apply_patches
2166 ~src:text_content
2167 ~patches:(PatchSet.elements env.patches)
2168 ~format_result:use_hh_format) end
2169 | None -> None
2171 (* function for patching statements *)
2172 let patch_stmt
2173 (text : program)
2174 (text_file : Relative_path.t)
2175 (text_content : string)
2176 (pattern_parsed : Parser_hack.parser_return)
2177 (transformations : patch_maps)
2178 ~(use_hh_format : bool) : string option =
2179 patch_expr_or_stmt text text_file text_content pattern_parsed transformations
2180 use_hh_format handle_stmt_skipany get_skipany_stmt
2182 (* function for patching expressions *)
2183 let patch_expr
2184 (text : program)
2185 (text_file : Relative_path.t)
2186 (text_content : string)
2187 (pattern_parsed : Parser_hack.parser_return)
2188 (transformations : patch_maps)
2189 ~(use_hh_format : bool) :
2190 string option =
2191 patch_expr_or_stmt text text_file text_content pattern_parsed transformations
2192 use_hh_format handle_expr_skipany get_skipany_expr
2194 let match_and_patch
2195 (text : program)
2196 (text_file : Relative_path.t)
2197 (text_content : string)
2198 (pattern_parsed : Parser_hack.parser_return)
2199 (transformations : patch_maps)
2200 ~(use_hh_format : bool) :
2201 string option =
2202 let res, env =
2203 match_ast_nodes
2204 (Program text)
2205 (Program pattern_parsed.Parser_hack.ast)
2206 { file = text_file;
2207 source = text_content;
2208 uses_regexp = false;
2209 comments = pattern_parsed.Parser_hack.comments;
2210 metavars = MetavarMap.empty;
2211 transformations = transformations;
2212 patches = PatchSet.empty } in
2213 match res with
2214 | NoMatch -> None
2215 | Matches _ ->
2216 if PatchSet.is_empty env.patches
2217 then None
2218 else
2219 Some (Patcher.apply_patches
2220 ~src:text_content
2221 ~patches:(PatchSet.elements env.patches)
2222 ~format_result:use_hh_format)
2224 let format_matches
2225 (matches : (ast_node * File_pos.t) list)
2226 (text_code : string) : string =
2227 let match_list =
2228 sort_and_remove_duplicates
2229 (fun (m1:(ast_node * File_pos.t))
2230 (m2:(ast_node * File_pos.t)) ->
2231 File_pos.line (snd m1) - File_pos.line (snd m2))
2232 matches in
2233 (* format a single match as "line num: line" *)
2234 let format_single_match (single_match : ast_node * File_pos.t) : string =
2235 let pos = snd single_match in
2236 if File_pos.is_dummy pos then "" else
2237 let line_num, bol_pos = File_pos.line_beg pos in
2238 let eol_pos = String.index_from text_code bol_pos '\n' in
2239 Printf.sprintf
2240 "%d: %s"
2241 line_num
2242 (String.sub text_code bol_pos (eol_pos - bol_pos)) in
2243 (String.concat
2244 "\n"
2245 (List.map ~f:format_single_match match_list))