Ctrl K
ComunidadGuías…

libs/ast_generic/AST_generic_helpers.ml

Rama main · réplica de lectura del repositorio original.

Archivo original ↗Preguntar a KiBot
(* Yoann Padioleau
 *
 * Copyright (C) 2019-2021 Semgrep Inc.
 *
 * This library is free software; you can redistribute it and/or
 * modify it under the terms of the GNU Lesser General Public License
 * version 2.1 as published by the Free Software Foundation, with the
 * special exception on linking described in file LICENSE.
 *
 * This library is distributed in the hope that it will be useful, but
 * WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the file
 * LICENSE for more details.
 *)
open Common
open AST_generic
module G = AST_generic
module Log = Log_ast_generic.Log

(*****************************************************************************)
(* Prelude *)
(*****************************************************************************)
(* Helpers to build or convert AST_generic elements.
 *
 * The very often used helper functions are actually in AST_generic.ml at
 * the end (e.g., AST_generic.basic_entity).
 * This module is for the more rarely used helpers.
 *)

(*****************************************************************************)
(* Helpers *)
(*****************************************************************************)

let str_of_ident = fst

let name_of_ids_with_opt_typeargs xs =
  match List.rev xs with
  | [] -> failwith "name_of_ids_with_opt_typeargs: empty ids"
  | [ (x, None) ] -> Id (x, empty_id_info ())
  | (x, topt) :: xs ->
      let qualif = if xs =*= [] then None else Some (QDots (xs |> List.rev)) in
      IdQualified
        {
          name_last = (x, topt);
          name_middle = qualif;
          name_top = None;
          name_info = empty_id_info ();
        }

(* internal *)
let name_to_qualifier = function
  | Id (id, _idinfo) -> [ (id, None) ]
  | IdQualified { name_last = id, topt; name_middle = qu; _ } ->
      let rest =
        match qu with
        | None -> []
        | Some (QDots xs) -> xs
        (* TODO, raise exn? *)
        | Some (QExpr _) -> []
      in
      rest @ [ (id, topt) ]

(* used for Parse_csharp_tree_sitter.ml
 * less: could move there? *)
let add_id_opt_type_args_to_name name (id, topt) =
  let qdots = name_to_qualifier name in
  IdQualified
    {
      name_last = (id, topt);
      name_middle = Some (QDots qdots);
      name_top = None;
      name_info = empty_id_info () (* TODO reuse from name?*);
    }

(* used for Parse_hack_tree_sitter.ml
 * less: could move there? *)
let add_type_args_to_name name type_args =
  match name with
  | Id (ident, id_info) ->
      (* Only IdQualified supports typeargs *)
      IdQualified
        {
          name_last = (ident, Some type_args);
          name_middle = None;
          name_top = None;
          name_info = id_info;
        }
  | IdQualified qualified_info -> (
      match qualified_info.name_last with
      | _id, Some _x ->
          IdQualified qualified_info
          (* TODO: Enable raise Impossible *)
          (* raise Impossible *)
          (* Never should have to overwrite type args, but also doesn't make sense to merge *)
      | id, None ->
          IdQualified { qualified_info with name_last = (id, Some type_args) })

let add_type_args_opt_to_name name topt =
  match topt with
  | None -> name
  | Some t -> add_type_args_to_name name t

(* NOTE: [~hidden] flags only the envelope; inner qualifier idents
   in [name_middle] are not marked hidden. Current prefilter visitor
   short-circuits at the envelope so this is fine — keep in mind if
   you add a visitor that descends into [name_middle]. *)
let name_of_ids ?name_top ?(case_insensitive = false) ?(hidden = false) xs =
  match List.rev xs with
  | [] -> failwith "name_of_ids: empty ids"
  | [ x ] when Option.is_none name_top ->
    Id (x, empty_id_info ~case_insensitive ~hidden ())
  | x :: xs ->
      let qualif =
        if xs =*= [] then None
        else Some (QDots (xs |> List.rev |> List_.map (fun id -> (id, None))))
      in
      IdQualified
        {
          name_last = (x, None);
          name_middle = qualif;
          name_top;
          name_info = empty_id_info ~case_insensitive ~hidden ();
        }

let add_suffix_to_name suffix name =
  match name with
  | Id (first_id, _) -> name_of_ids [ first_id; suffix ]
  | IdQualified ({ name_last; name_middle; _ } as q_info) ->
      let new_name_middle =
        match name_middle with
        | Some (QDots qualifiers) -> Some (G.QDots (qualifiers @ [ name_last ]))
        | Some (QExpr (expr, tok)) ->
            Some (G.QExpr ({ expr with e = N name }, tok))
        | None -> Some (G.QDots [ name_last ])
      in
      IdQualified
        {
          q_info with
          name_last = (suffix, None);
          name_middle = new_name_middle;
        }

let name_of_id ?(case_insensitive = false) ?(hidden = false) id =
  Id (id, empty_id_info ~case_insensitive ~hidden ())

let name_of_dot_access e =
  let rec fetch_ids = function
    | G.N (G.Id (x, _)) -> Some [ x ]
    | G.N (G.IdQualified { name_last; name_middle; _ }) ->
        let* name_middle =
          match name_middle with
          | None -> Some []
          | Some (QDots xs) -> Some (List_.map (fun (id, _) -> id) xs)
          | Some (QExpr _) -> None
        in
        let name_last = fst name_last in
        Some (name_middle @ [ name_last ])
    | G.DotAccess (e1, _, G.FN (G.Id (x, _))) ->
        let* xs = fetch_ids e1.e in
        Some (xs @ [ x ])
    | ___else___ -> None
  in
  let* xs = fetch_ids e.e in
  Some (name_of_ids xs)

let name_of_entity_name (en : entity_name) : name option =
  match en with
  | EN name -> Some name
  | EDynamic ({ e = DotAccess _; _ } as e) -> name_of_dot_access e
  | EDynamic _
  | EPattern _
  | OtherEntity _ ->
      None

(* TODO: you should not need to use that. This is mostly because
 * Constructor and PatConstructor currently takes a dotted_ident instead
 * of a name, and because module_name accepts only DottedName
 * but C# and C++ allow names.
 *)
let dotted_ident_of_name (n : name) : dotted_ident =
  match n with
  | Id (id, _) -> [ id ]
  | IdQualified { name_last = ident, _toptTODO; name_middle; _ } ->
      let before =
        match name_middle with
        (* we skip the type parts in ds ... *)
        | Some (QDots ds) -> ds |> List_.map fst
        | Some (QExpr _) ->
            Log.warn (fun m -> m "unexpected qualifier type");
            []
        | None -> []
      in
      before @ [ ident ]

(* Duplicate of the one in AST_generic, but with an optimisation
 * we cannot do there because tests would fail. Leaving this here
 * for now. *)
let expr_to_stmt e =
  match e.e with
  | StmtExpr stmt -> stmt
  | _ -> ExprStmt (e, sc) |> G.s

let is_context_arg (a : argument) : bool =
  match a with
  (* the expression, which a Swift `using:` label, say, does not have *)
  | OtherArg ((tag, _), [ E _ ]) -> String.equal tag using_tag
  | _ -> false

let is_context_param (p : parameter) : bool =
  match p with
  | Param { pattrs; _ } ->
      List.exists
        (fun (attr : attribute) ->
          match attr with
          | OtherAttribute ((tag, _), []) -> String.equal tag using_tag
          | _ -> false)
        pattrs
  | _ -> false

(* A [PatList] / [PatConstructor] holds "map pairs" when every element is a
 * [PatKeyVal] in the canonical [(lookup_key, binding_pattern)] order,
 * possibly wrapped by exactly one [OtherPat("MapPairKeyword" |
 * "MapPairArrow", …)]. That tag is emitted by the Elixir parser to
 * preserve the source-level [:] vs [=>] distinction for rule matching;
 * no other parser uses it, and no parser nests it. *)
let is_map_pair_pattern (p : pattern) : bool =
  match p with
  | PatKeyVal (_, _) -> true
  | OtherPat
      ( (("MapPairKeyword" | "MapPairArrow"), _),
        [ P (PatKeyVal (_, _)) ] ) ->
      true
  | _ -> false

(* The tag of a Java switch rule (`case 1 -> s`) among the cases of its
 * branch: the branch has the form of a switch rule. *)
let is_switch_rule_arrow (c : case) : bool =
  match c with
  | OtherCase (("->", _), []) -> true
  | _ -> false

(* A non-empty list of map pairs ([is_map_pair_pattern]). *)
let is_map_pair_list (ps : pattern list) : bool =
  match ps with
  | [] -> false
  | _ :: _ -> List.for_all is_map_pair_pattern ps

(* The [PatKeyVal] of a map pair, without Elixir's map-pair tag. *)
let unwrap_map_pair_pattern (p : pattern) : pattern =
  match p with
  | OtherPat
      ( (("MapPairKeyword" | "MapPairArrow"), _),
        [ P (PatKeyVal (_, _) as inner) ] ) ->
      inner
  | _ -> p

(* A map pattern whose entries (map pairs and, in a rule, `...`) would
 * otherwise read as a list pattern, as an empty map or a map of only `...`
 * does: the entries, then the closing bracket, tagged "MapPattern" with the
 * opening bracket, or "ClosedMapPattern" for a pattern that matches only a
 * map with no other keys (Ruby `in {}`, `**nil`). *)
let map_pattern ?(closed : bool = false)
    ((l, entries, r) : pattern list bracket) : pattern =
  let tag = if closed then "ClosedMapPattern" else "MapPattern" in
  OtherPat ((tag, l), List_.map (fun p -> P p) entries @ [ Tk r ])

let is_closed_map_pattern (p : pattern) : bool =
  match p with
  | OtherPat (("ClosedMapPattern", _), _) -> true
  | _ -> false

(* The bracketed entries of a pattern built by [map_pattern]. *)
let map_pattern_entries (p : pattern) : pattern list bracket option =
  let rec entries acc = function
    | [ Tk r ] -> Some (List.rev acc, r)
    | P e :: rest -> entries (e :: acc) rest
    | _ -> None
  in
  match p with
  | OtherPat ((("MapPattern" | "ClosedMapPattern"), l), anys) ->
      Option.map (fun (es, r) -> (l, es, r)) (entries [] anys)
  | _ -> None

(* In Go/Swift a pattern can be a complex expressions. It is just
 * matched for equality with the thing it's matched against, so in that
 * case it should be a pattern like | _ when expr = x.
 * For Python you can actually have a PatDisj of exception classes.
 * coupling: see pattern_to_expr below
 *)
let rec expr_to_pattern e =
  match e.e with
  | N (Id (id, info)) -> PatId (id, info)
  | Container (Tuple, (t1, xs, t2)) ->
      PatTuple (t1, xs |> List_.map expr_to_pattern, t2)
  | L l -> PatLiteral l
  | Container ((List | Array), (t1, xs, t2)) ->
      PatList (t1, xs |> List_.map expr_to_pattern, t2)
  (* JS/TS rest binding [...rest_target] inside a destructure. Recogniser
   * gating by lang lives downstream in [AST_to_IL.split_trailing_rest]. *)
  | Call ({ e = IdSpecial (Spread, tok); _ }, (_, [ Arg rest_target ], _)) ->
      PatConstructor
        (Id (("...", tok), empty_id_info ()), [ expr_to_pattern rest_target ])
  (* Dict destructuring (e.g. Python [{"a": x}], Elixir map patterns
   * after the frontend lowers them). Each entry is
   * [Container(Tuple, [key; val])]. Entries whose key is a static
   * ident / string / atom are routed through [PatRecord]; the
   * [AST_to_IL.PatRecord] lowering emits a [Dot] offset per field,
   * which is what makes field-sensitive lookups and destructured HOF
   * callbacks work end-to-end.
   *
   * Entries with non-static keys are dropped here; their values never
   * bind usefully through pattern destructuring. *)
  | Container (Dict, (t1, xs, t2)) ->
      let fields =
        List.filter_map
          (fun entry ->
            match entry.e with
            | Container (Tuple, (_, [ k; v ], _)) -> (
                let key_ident =
                  match k.e with
                  | L (String (_, (s, tok), _)) -> Some (s, tok)
                  | L (Atom (tok, (s, _))) -> Some (s, tok)
                  | N (Id (id, _)) -> Some id
                  | _ -> None
                in
                match key_ident with
                | Some ident -> Some ([ ident ], expr_to_pattern v)
                | None -> None)
            | _ -> None)
          xs
      in
      PatRecord (t1, fields, t2)
  (* Object/record literal used as a destructuring pattern (JS/TS
   * [{cb, data}] or [{cb: handler}]). Each field is a [DefStmt] whose
   * entity name is the field label and whose [vinit] (or the
   * [FieldDefColon]'s [vinit]) is the value pattern.
   *
   * For JS shorthand [{cb}], the field's [vinit] is a reference [N (Id
   * ("cb", _))]; recursing into [expr_to_pattern] on that turns the
   * reference into a [PatId] binding, which Naming_AST later resolves. *)
  | Record (t1, fields, t2) ->
      let pat_fields =
        List.filter_map
          (fun f ->
            match f with
            | F
                {
                  s =
                    DefStmt
                      ( { name = EN (Id (id, _)); tparams = None; _ },
                        ( VarDef { vinit = Some v_expr; _ }
                        | FieldDefColon { vinit = Some v_expr; _ } ) );
                  _;
                } ->
                Some ([ id ], expr_to_pattern v_expr)
            | _ -> None)
          fields
      in
      PatRecord (t1, pat_fields, t2)
  | Constructor (n, (_, args, _)) ->
      PatConstructor (n, args |> List_.map expr_to_pattern)
  | Ellipsis t -> PatEllipsis t
  (* a skipped element of a destructuring target, as in '[, $b] = $a'
   * coupling: PatternHole in php_to_generic.ml, js_to_generic.ml,
   * Parse_hack_tree_sitter.ml, Parse_solidity_tree_sitter.ml *)
  | OtherExpr (("PatternHole", tok), []) -> OtherPat (("PatternHole", tok), [])
  | OtherExpr (tag, [ E e ]) -> OtherPat (tag, [ P (expr_to_pattern e) ])
  | Cast (ty, _tok, expr) -> PatTyped (expr_to_pattern expr, ty)
  | LetPattern (p, {e = N (Id (i, info)); _} ) -> PatAs (p, (i, info))
  (* coupling: emitted by Python_to_generic for `case ... if guard:`. *)
  | OtherExpr (("CasePatWhen", _), [ E inner; E guard ]) ->
      PatWhen (expr_to_pattern inner, guard)
  | _ -> OtherPat (("ExprToPattern", fake ""), [ E e ])

exception NotAnExpr

(* sgrep: this is to treat pattern metavars as expr metavars *)
let rec pattern_to_expr p =
  (match p with
  | PatId (id, info) -> N (Id (id, info))
  | PatTuple (t1, xs, t2) ->
      Container (Tuple, (t1, xs |> List_.map pattern_to_expr, t2))
  | PatLiteral l -> L l
  | PatList (t1, xs, t2) ->
      Container (List, (t1, xs |> List_.map pattern_to_expr, t2))
  | PatConstructor (n, ps) ->
      Constructor (n, Tok.unsafe_fake_bracket (ps |> List_.map pattern_to_expr))
  | PatEllipsis t -> Ellipsis t
  | PatTyped (p, ty) -> Cast (ty, fake "", pattern_to_expr p)
  | OtherPat (("ExprToPattern", _), [ E e ]) -> e.e
  (* A pattern built by [map_pattern] reads as a map. *)
  | OtherPat ((("MapPattern" | "ClosedMapPattern"), _), _) -> (
      match map_pattern_entries p with
      | Some (t1, xs, t2) ->
          Container (Dict, (t1, xs |> List_.map pattern_to_expr, t2))
      | None -> raise NotAnExpr)
  | OtherPat (tag, [ P p ]) -> OtherExpr (tag, [ E (pattern_to_expr p) ])
  | _ -> raise NotAnExpr)
  |> G.e

(* Primarily for usage in converting an Assign into a DefStmt. We fail to
 * produce an entity name if we don't definitely have something which is
 * a name.
 *)
let expr_to_entity_name_opt (e : G.expr) : G.entity_name option =
  match e.G.e with
  | N name -> Some (EN name)
  | _ -> None

let entity_of_pattern ?(attrs = []) (pat : G.pattern) : G.entity =
  (* TODO Desugar single-element tuples? *)
  let entity_name =
    match pat with
    (* Unwrap PatId into a regular name. map_property_binding_pattern has to
     * return a pattern because it is recursive, and might need to compose its
     * return value into larger patterns. So, we have to unwrap here. *)
    | G.PatId (id, id_info) -> G.EN (G.Id (id, id_info))
    | pattern -> G.EPattern pattern
  in
  { G.name = entity_name; attrs; tparams = None }

(* We would like to do more things here, like transform certain
 * N in TyN, but we can't do that from the Xxx_to_generic.ml
 * (e.g., Python_to_generic.ml). Indeed, certain transformations
 * require Naming_AST to have correctly resolved certain Ids.
 * See Graph_code_AST_xxx.expr_to_type_after_naming() below for that situation.
 *)
let expr_to_type e = TyExpr e |> G.t

(* TODO: recognize foo(args)? like in Kotlin/Java *)
let expr_to_class_parent e : class_parent = (expr_to_type e, None)

(* See also exprstmt, and stmt_to_expr in AST_generic.ml *)

let cond_to_expr = function
  | Cond e -> e
  | OtherCond (categ, xs) -> OtherExpr (categ, xs) |> G.e

(* old: there was a stmt_to_item before *)
(* old: there was a stmt_to_field before *)

(* see also Java_to_generic.entity_to_param *)
(* see also Python_to_generic.expr_to_attribute *)
(* see also Php_generic.list_expr_to_opt *)
(* see also Php_generic.name_of_qualified_ident (also in Java) *)

let opt_to_label_ident = function
  | None -> LNone
  | Some id -> LId id

(* used in abstract interpreter and type for PHP where we now reuse
 * 'AST_generic.arithmetic_operator' above *)
(*
let is_boolean_operator = function
  | Plus (* unary too *) | Minus (* unary too *)
  | Mult | Div | Mod
  | Pow | FloorDiv | MatMult (* Python *)
  | LSL | LSR | ASR (* L = logic, A = Arithmetic, SL = shift left *)
  | BitOr | BitXor | BitAnd | BitNot | BitClear (* unary *)
  | Range | Nullish | NotNullPostfix | Elvis | Length
  | RangeInclusive
    -> false
  | And | Or | Xor | Not
  | Eq     | NotEq
  | PhysEq | NotPhysEq
  | Lt | LtE | Gt | GtE
  | Cmp | Concat | Append
  | RegexpMatch | NotMatch
  | In | NotIn | Is | NotIs
    -> true
*)

(* this will be used for the lhs of an Assign. Maybe one day Assign
 * will have a more precise type and we can be more precise too here.
 *)
let entity_name_to_expr name idinfo_opt =
  match (name, idinfo_opt) with
  (* assert idinfo = _idinfo below? *)
  | EN (Id (id, idinfo)), None -> N (Id (id, idinfo)) |> G.e
  | EN (Id (id, _idinfo)), Some idinfo -> N (Id (id, idinfo)) |> G.e
  | EN (IdQualified n), None -> N (IdQualified n) |> G.e
  | EN (IdQualified n), Some idinfo ->
      N (IdQualified { n with name_info = idinfo }) |> G.e
  | EDynamic e, _ -> e
  | EPattern pat, _ -> G.OtherExpr (("EPattern", fake ""), [ P pat ]) |> G.e
  | OtherEntity (categ, xs), _ -> G.OtherExpr (categ, xs) |> G.e

let argument_to_expr arg =
  match arg with
  | Arg e -> e
  | ArgKwd (id, e)
  | ArgKwdOptional (id, e) ->
      let n = name_of_id id in
      let k = N n |> G.e in
      G.keyval k (fake "") e
  | ArgType _
  | OtherArg _ ->
      raise NotAnExpr

(* used in controlflow_build and semgrep *)
let vardef_to_assign (ent, def) =
  let name_or_expr = entity_name_to_expr ent.name None in
  let v =
    match def.vinit with
    | Some v -> v
    | None -> L (Null (Tok.unsafe_fake_tok "null")) |> G.e
  in
  Assign (name_or_expr, Tok.unsafe_fake_tok "=", v) |> G.e

(* TODO: pass also semicolon tok? not just the equal tok *)
let assign_to_vardef_opt ((e1, _teq, e2) : G.expr * G.tok * G.expr) =
  match e1.G.e with
  | Cast (ty, _, e) ->
      let* name = expr_to_entity_name_opt e in
      let ent = { name; attrs = []; tparams = None } in
      Some
        (DefStmt (ent, VarDef { vinit = Some e2; vtype = Some ty; vtok = no_sc })
        |> G.s)
  | _ ->
      let* name = expr_to_entity_name_opt e1 in
      let ent = { name; attrs = []; tparams = None } in
      Some
        (DefStmt (ent, VarDef { vinit = Some e2; vtype = None; vtok = no_sc })
        |> G.s)

(* used in controlflow_build *)
let funcdef_to_lambda (ent, def) resolved =
  let idinfo = { (empty_id_info ()) with id_resolved = ref resolved } in
  let name_or_expr = entity_name_to_expr ent.name (Some idinfo) in
  let v = Lambda def |> G.e in
  Assign (name_or_expr, Tok.unsafe_fake_tok "=", v) |> G.e

let funcbody_to_stmt = function
  | FBStmt st -> st
  | FBExpr e -> G.exprstmt e
  | FBDecl sc -> Block (sc, [], sc) |> G.s
  | FBNothing -> Block (Tok.unsafe_fake_bracket []) |> G.s

let has_keyword_attr kwd attrs =
  attrs
  |> List.exists (function
       | KeywordAttr (kwd2, _) -> kwd =*= kwd2
       | _ -> false)

let id_of_name = function
  | G.Id (id, id_info) -> (id, id_info)
  | G.IdQualified { G.name_last = id, _typeargs; name_info = id_info; _ } ->
      (id, id_info)

let name_is_global = function
  | Global (* OSS *)
  | GlobalName _ (* Pro *)
  | EnclosedVar (* OSS / a class variable *) ->
      true
  | LocalVar
  | Parameter
  | ImportedEntity _
  | ImportedModule _
  | TypeName
  | Macro
  | EnumConstant ->
      false

(* just used in cpp_to_generic.ml for now, could be moved there *)
let parameter_to_catch_exn_opt p =
  match p with
  | Param p -> Some (CatchParam p)
  | ParamEllipsis t -> Some (CatchPattern (PatEllipsis t))
  | ParamPattern (p, _) -> Some (G.CatchPattern p)
  (* TODO: valid in exn spec? *)
  | ParamRest (_, _p)
  | ParamHashSplat (_, _p) ->
      None
  | ParamReceiver _
  | OtherParam _ ->
      None

let ctype_of_literal = function
  | G.Bool _ -> G.Cbool
  | G.Int _ -> G.Cint
  | G.String _ -> G.Cstr
  | ___else___ -> G.Cany

(* TODO: The order of fields declaration in a class matter only in
 * some extremely rare cases. Since we need to deal with forward
 * references, we first process fields, before we touch methods.
 * It's a super-simple heuristic, a proper two-pass approach is
 * yet to be implemented. *)
let reorder_fields_in_class_definition (c : class_definition) : class_definition =
  let is_def_var d =
    match d with
    | F { s = (DefStmt (_, VarDef _)); _ } -> true
    | _ -> false
  in
  let (left_tok, body, right_tok) = c.cbody in
  let new_body =
    List_.exclude is_def_var body
    |> List_.filter_append is_def_var body
  in
  {c with cbody = (left_tok, new_body, right_tok)}

(*****************************************************************************)
(* Abstract position and svalue for comparison *)
(*****************************************************************************)

(* update: you should now use AST_generic.equal_any which internally
 * does not care about position information.
 * TODO: can we remove this function now then?
 *)
let abstract_for_comparison_visitor =
  object
    inherit [_] AST_generic.map_legacy as super
    method! visit_tok _env _i = Tok.Ab

    method! visit_id_info env ii =
      super#visit_id_info env { ii with AST_generic.id_svalue = ref None }
  end

let abstract_for_comparison_any x =
  abstract_for_comparison_visitor#visit_any () x

(*****************************************************************************)
(* Associative-Commutative (AC) matching *)
(*****************************************************************************)

let is_associative_operator op =
  match op with
  | Or
  | And
  | BitOr
  | BitAnd
  | BitXor
  | Concat ->
      true
  (* TODO: Plus, Mult, ... *)
  | __else__ -> false

let ac_matching_nf op args =
  (* yes... here we use exceptions like a "goto" to avoid the option monad *)
  let rec nf args1 =
    args1
    |> List_.map (function
         | Arg e -> e
         | ArgKwd _
         | ArgKwdOptional _
         | ArgType _
         | OtherArg _ ->
             raise_notrace Exit)
    |> List_.map nf_one |> List_.flatten
  and nf_one e =
    match e.e with
    | Call ({ e = IdSpecial (Op op1, _tok1); _ }, (_, args1, _)) when op =*= op1
      ->
        nf args1
    | _ -> [ e ]
  in
  if is_associative_operator op then (
    try Some (nf args) with
    | Exit ->
        Log.warn (fun m ->
            m "ac_matching_nf: %s(%s): unexpected ArgKwd | ArgType | ArgOther"
              (show_operator op)
              (show_arguments (Tok.unsafe_fake_bracket args)));
        None)
  else None

let undo_ac_matching_nf tok op : expr list -> expr option = function
  | [] -> None
  | [ arg ] -> Some arg
  | a1 :: a2 :: args ->
      let mk_op x y =
        Call
          ( IdSpecial (Op op, tok) |> G.e,
            Tok.unsafe_fake_bracket [ Arg x; Arg y ] )
        |> G.e
      in
      Some (List.fold_left mk_op (mk_op a1 a2) args)

let set_e_range l r e =
  match (Tok.loc_of_tok l, Tok.loc_of_tok r) with
  | Ok l, Ok r -> e.e_range <- Some (l, r)
  | Error _, _
  | _, Error _ ->
      (* Probably not super useful to dump the whole expression, or to log the
       * fake tokens themselves. Perhaps this will be useful for debugging
       * isolated examples, though. *)
      Log.warn (fun m -> m "set_e_range failed: missing token location");
      ()

(* coupling: you might want to update range_visitor if you update this *)
class ['self] extract_info_visitor =
  object (_self : 'self)
    inherit ['self] AST_generic.iter_no_id_info as super
    method! visit_tok globals tok = Stack_.push tok globals

    method! visit_expr globals x =
      match x.e with
      (* Ignore the tokens from the expression str is aliased to *)
      | Alias ((_str, t), _e) -> Stack_.push t globals
      | _ -> super#visit_expr globals x
  end

let extract_info_visitor_instance = new extract_info_visitor
let ii_of_any any =
  let v = extract_info_visitor_instance in
  let globals = ref [] in
  v#visit_any globals any;
  List.rev !globals
[@@profiling]

let info_of_any any =
  match ii_of_any any with
  | x :: _ -> x
  | [] -> assert false

(*e: function [[Lib_AST.ii_of_any]] *)

let first_info_of_any any =
  let xs = ii_of_any any in
  let xs = List.filter Tok.is_origintok xs in
  let min, _max = Tok_range.min_max_toks_by_pos xs in
  min

(*****************************************************************************)
(* Extract ranges *)
(*****************************************************************************)

(* Also used below by `nearest_any_of_pos` *)
let smaller t1 t2 =
  if compare t1.Tok.pos.bytepos t2.Tok.pos.bytepos < 0 then t1 else t2

let larger t1 t2 =
  if compare t1.Tok.pos.bytepos t2.Tok.pos.bytepos > 0 then t1 else t2

let incorporate_tokens ranges (left, right) =
  match !ranges with
  | None -> ranges := Some (left, right)
  | Some (orig_left, orig_right) ->
      ranges := Some (smaller orig_left left, larger orig_right right)

let incorporate_token ranges tok =
  if Tok.is_origintok tok then
    let tok_loc = Tok.unsafe_loc_of_tok tok in
    incorporate_tokens ranges (tok_loc, tok_loc)

(* coupling: you might want to update extract_info_visitor if you update this *)
class ['self] range_visitor =
  object (self : 'self)
    inherit ['self] AST_generic.iter_no_id_info as super
    method! visit_tok ranges tok = incorporate_token ranges tok

    method! visit_expr ranges expr =
      match expr.e_range with
      | None -> (
          let saved_ranges = !ranges in
          ranges := None;
          super#visit_expr ranges expr;
          expr.e_range <- !ranges;
          match saved_ranges with
          | None -> ()
          | Some r -> incorporate_tokens ranges r)
      | Some range -> incorporate_tokens ranges range

    method! visit_stmt ranges stmt =
      match stmt.s_range with
      | None -> (
          let saved_ranges = !ranges in
          ranges := None;
          super#visit_stmt ranges stmt;
          stmt.s_range <- !ranges;
          match saved_ranges with
          | None -> ()
          | Some r -> incorporate_tokens ranges r)
      | Some range -> incorporate_tokens ranges range

    (* Ignore the tokens from the aliased expression *)
    method! visit_Alias ranges id _e = self#visit_ident ranges id
  end

let extract_ranges_with_anys :
    AST_generic.any list -> (Tok.location * Tok.location) option =
  let v = new range_visitor in
  fun anys ->
    let ranges = ref None in
    List.iter (v#visit_any ranges) anys;
    !ranges

let extract_ranges any = extract_ranges_with_anys [ any ]

let set_e_range_with_anys anys e =
  match extract_ranges_with_anys anys with
  | Some (l, r) -> e.e_range <- Some (l, r)
  | None ->
      Log.warn (fun m -> m "set_e_range_with_anys failed: no locations found");
      ()

let range_of_tokens_unsafe tokens =
  List.filter Tok.is_origintok tokens |> Tok_range.min_max_toks_by_pos
[@@profiling]

let range_of_tokens tokens =
  match List.filter Tok.is_origintok tokens with
  | [] -> None
  | tokens -> Some (Tok_range.min_max_toks_by_pos tokens)
[@@profiling]

let range_of_any_opt any =
  (* Even if the ranges are cached, calling `extract_ranges` to get them
   * is extremely expensive (due to `mk_visitor`). Testing taint-mode
   * open-redirect rule on Django, we spent ~16 seconds computing range
   * info (despite caching). If we bypass `extract_ranges` as we do here,
   * that time drops to just ~1.5 seconds! *)
  match any with
  | G.E e when Option.is_some e.e_range -> e.e_range
  | G.S s when Option.is_some s.s_range -> s.s_range
  | G.Tk tok -> (
      match Tok.loc_of_tok tok with
      | Ok tok_loc -> Some (tok_loc, tok_loc)
      | Error _ -> None)
  | G.Anys [] -> None
  | _ -> extract_ranges any
[@@profiling]

(*****************************************************************************)
(* Nearest Any node of a position *)
(*****************************************************************************)

type any_range = {
  range : (Tok.location * Tok.location) option ref;
  any : (AST_generic.any * (Tok.location * Tok.location)) option ref;
  position : int; (* charpos *)
}

class ['self] any_range_visitor =
  let pos_within pos (t1', t2') =
    let _, _, t2'_charpos = Tok.end_pos_of_loc t2' in
    pos >= t1'.Tok.pos.bytepos && pos <= t2'_charpos
  in
  let range_within (t1, t2) (t1', t2') =
    let _, _, t2_charpos = Tok.end_pos_of_loc t2 in
    let _, _, t2'_charpos = Tok.end_pos_of_loc t2' in
    t1.Tok.pos.bytepos >= t1'.Tok.pos.bytepos && t2_charpos <= t2'_charpos
  in
  let set_any_range info (any, range) =
    let charpos = info.position in
    match (!(info.any), range) with
    | _, None -> ()
    | None, Some range ->
        if pos_within charpos range then info.any := Some (any, range)
    | Some (_, cur_range), Some range ->
        if range_within range cur_range && pos_within charpos range then
          info.any := Some (any, range)
  in
  let handle_any info any visit_fn =
    let saved_ranges = !(info.range) in
    info.range := None;
    visit_fn ();
    set_any_range info (any, !(info.range));
    match saved_ranges with
    | None -> ()
    | Some range -> incorporate_tokens info.range range
  in
  object (self : 'self)
    inherit ['self] AST_generic.iter_no_id_info as super
    method! visit_tok info tok = incorporate_token info.range tok

    method! visit_type_ info type_ =
      handle_any info (T type_) (fun () -> super#visit_type_ info type_)

    method! visit_pattern info pat =
      handle_any info (P pat) (fun () -> super#visit_pattern info pat)

    method! visit_field info field =
      handle_any info (Fld field) (fun () -> super#visit_field info field)

    method! visit_argument info arg =
      handle_any info (Ar arg) (fun () -> super#visit_argument info arg)

    method! visit_directive info directive =
      handle_any info (Dir directive) (fun () ->
          super#visit_directive info directive)

    method! visit_attribute info attr =
      handle_any info (At attr) (fun () -> super#visit_attribute info attr)

    method! visit_definition info def =
      handle_any info (Def def) (fun () -> super#visit_definition info def)

    method! visit_parameter info param =
      handle_any info (Pa param) (fun () -> super#visit_parameter info param)

    method! visit_expr info expr =
      handle_any info (E expr) (fun () -> super#visit_expr info expr)

    method! visit_stmt info stmt =
      handle_any info (S stmt) (fun () -> super#visit_stmt info stmt)

    (* Ignore the tokens from the aliased expression *)
    method! visit_Alias ranges id _e = self#visit_ident ranges id
  end

let any_range_visitor_instance = new any_range_visitor
let nearest_any_of_pos prog position =
  let v = any_range_visitor_instance in
  let info = { range = ref None; any = ref None; position } in
  v#visit_program info prog;
  !(info.any)

(*****************************************************************************)
(* Fix token locations *)
(*****************************************************************************)

let fix_token_locations_visitor =
  object (_self : 'self)
    inherit [_] AST_generic.map_legacy as super

    method! visit_id_info _fix ii =
      (* The id_info contains locations that should not be modified, and they
       * are likely outside the sub-AST of interest anyways. *)
      ii

    method! visit_expr fix e =
      super#visit_expr fix
        { e with e_range = Option.map (fun (x, y) -> (fix x, fix y)) e.e_range }

    method! visit_stmt fix s =
      super#visit_stmt fix
        { s with s_range = Option.map (fun (x, y) -> (fix x, fix y)) s.s_range }

    method! visit_tok fix t = Tok.fix_location fix t
  end

(* Exposing just these methods because we want the .mli file for this module,
 * and it's impractical to write out the type of an autogenerated visitor. If we
 * need more, we could consider putting the visitor in a separate file without
 * an mli and allowing direct access to it. *)
let fix_token_locations_any = fix_token_locations_visitor#visit_any
let fix_token_locations_program = fix_token_locations_visitor#visit_program

(*****************************************************************************)
(* Add semicolons *)
(*****************************************************************************)

let add_semicolon_to_last_var_def_and_convert_to_stmts (sc : sc)
    (xs : (entity * variable_definition) list) : stmt list =
  let ys =
    match List.rev xs with
    (* Impossible in principle *)
    | [] -> []
    | (ent, vardef) :: xs -> (ent, { vardef with vtok = Some sc }) :: xs
  in
  ys |> List_.map (fun (ent, vardef) -> DefStmt (ent, VarDef vardef) |> G.s)

let add_semicolon_to_last_def_and_convert_to_stmts (sc : sc)
    (xs : definition list) : stmt list =
  let ys =
    match List.rev xs with
    (* Impossible in principle *)
    | [] -> []
    | (ent, VarDef vardef) :: xs ->
        (ent, VarDef { vardef with vtok = Some sc }) :: xs
    | xs -> xs
  in
  ys |> List_.map (fun (ent, def) -> DefStmt (ent, def) |> G.s)

let ident_of_parameter_opt (p : parameter) : AST_generic.ident option =
  match p with
  | Param pc
  | ParamRest (_, pc)
  | ParamHashSplat (_, pc)
  | ParamReceiver pc ->
      pc.pname
  | _ -> None
    
let any_of_function_body (fb : function_body) : any =
  match fb with
  | FBStmt s -> S s
  | FBExpr e -> E e
  | FBDecl t -> Tk t
  | FBNothing -> Anys []