<commit_before>(*
 * Copyright (c) Meta Platforms, Inc. and affiliates.
 *
 * This source code is licensed under the MIT license found in the
 * LICENSE file in the root directory of this source tree.
 *)

module Ast = Flow_ast
open Hint_api
open Reason
open Flow_ast_mapper
open Loc_collections
module EnvMap = Env_api.EnvMap

type cond_context =
  | NonConditionalContext
  | SwitchConditionalTest of {
      case_test_reason: reason;
      switch_discriminant_reason: reason;
    }
  | OtherConditionalTest

type scope_kind =
  | Ordinary (* function or module *)
  | Async (* async function *)
  | Generator (* generator function *)
  | AsyncGenerator (* async generator function *)
  | Module (* module scope *)
  | Predicate (* predicate function *)
  | Ctor

type class_stack = ALoc.t list

type for_kind =
  | In
  | Of of { await: bool }

(* A map from location of tparam to name. *)
type tparams_map = string ALocMap.t

type hint_node =
  | AnnotationHint of tparams_map * (ALoc.t, ALoc.t) Ast.Type.annotation
  | ValueHint of (ALoc.t, ALoc.t) Ast.Expression.t
  | ProvidersHint of ALoc.t Nel.t

type root =
  | Annotation of {
      tparams_map: tparams_map;
      optional: bool;
      default_expression: (ALoc.t, ALoc.t) Ast.Expression.t option;
      is_assignment: bool;
      annot: (ALoc.t, ALoc.t) Ast.Type.annotation;
    }
  | Value of (ALoc.t, ALoc.t) Ast.Expression.t
  | Contextual of {
      reason: Reason.reason;
      hint: hint_node hint;
      default_expression: (ALoc.t, ALoc.t) Ast.Expression.t option;
    }
  | Catch
  | For of for_kind * (ALoc.t, ALoc.t) Ast.Expression.t

type selector =
  | Elem of int
  | Prop of {
      prop: string;
      prop_loc: ALoc.t;
      has_default: bool;
    }
  | Computed of (ALoc.t, ALoc.t) Ast.Expression.t
  | ObjRest of {
      used_props: string list;
      after_computed: bool;
    }
  | ArrRest of int
  | Default

type default =
  | DefaultExpr of (ALoc.t, ALoc.t) Ast.Expression.t
  | DefaultCons of (ALoc.t, ALoc.t) Ast.Expression.t * default
  | DefaultSelector of default * selector

type binding =
  | Root of root
  | Select of {
      selector: selector;
      default: default option;
      binding: binding;
    }

type import =
  | Named of {
      kind: Ast.Statement.ImportDeclaration.import_kind option;
      remote: string;
      remote_loc: ALoc.t;
      local: string;
    }
  | Namespace
  | Default of string

type generator_annot = {
  tparams_map: tparams_map;
  return_annot: (ALoc.t, ALoc.t) Ast.Type.annotation;
  async: bool;
}

type class_implicit_this_tparam = {
  tparams_map: tparams_map;
  class_tparams_loc: ALoc.t option;
}

type def =
  | Binding of binding
  | ChainExpression of cond_context * (ALoc.t, ALoc.t) Ast.Expression.t
  | RefiExpression of (ALoc.t, ALoc.t) Ast.Expression.t
  | MemberAssign of {
      member_loc: ALoc.t;
      member: (ALoc.t, ALoc.t) Ast.Expression.Member.t;
      rhs: (ALoc.t, ALoc.t) Ast.Expression.t;
    }
  | OpAssign of {
      exp_loc: ALoc.t;
      lhs: (ALoc.t, ALoc.t) Ast.Pattern.t;
      op: Ast.Expression.Assignment.operator;
      rhs: (ALoc.t, ALoc.t) Ast.Expression.t;
    }
  | Update of {
      exp_loc: ALoc.t;
      lhs_member: (ALoc.t, ALoc.t) Ast.Expression.t option;
      op: Ast.Expression.Update.operator;
    }
  | Function of {
      hint: hint_node hint;
      synthesizable_from_annotation: bool;
      has_this_def: bool;
      function_loc: ALoc.t;
      function_: (ALoc.t, ALoc.t) Ast.Function.t;
      tparams_map: tparams_map;
    }
  | Class of {
      class_implicit_this_tparam: class_implicit_this_tparam;
      class_: (ALoc.t, ALoc.t) Ast.Class.t;
      class_loc: ALoc.t;
    }
  | DeclaredClass of ALoc.t * (ALoc.t, ALoc.t) Ast.Statement.DeclareClass.t
  | TypeAlias of ALoc.t * (ALoc.t, ALoc.t) Ast.Statement.TypeAlias.t
  | OpaqueType of ALoc.t * (ALoc.t, ALoc.t) Ast.Statement.OpaqueType.t
  | TypeParam of tparams_map * (ALoc.t, ALoc.t) Ast.Type.TypeParam.t
  | Interface of ALoc.t * (ALoc.t, ALoc.t) Ast.Statement.Interface.t
  | Enum of ALoc.t * ALoc.t Ast.Statement.EnumDeclaration.body
  | Import of {
      import_kind: Ast.Statement.ImportDeclaration.import_kind;
      import: import;
      source: string;
      source_loc: ALoc.t;
    }
  | GeneratorNext of generator_annot option
  | DeclaredModule of ALoc.t * (ALoc.t, ALoc.t) Ast.Statement.DeclareModule.t

module Print = struct
  open Utils_js

  let string_of_root = function
    | Contextual _ -> "contextual"
    | Catch -> "catch"
    | Annotation { annot = (loc, _); _ } -> spf "annot %s" (ALoc.debug_to_string loc)
    | Value (loc, _) -> spf "val %s" (ALoc.debug_to_string loc)
    | For (In, (loc, _)) -> spf "for in %s" (ALoc.debug_to_string loc)
    | For (Of _, (loc, _)) -> spf "for of %s" (ALoc.debug_to_string loc)

  let string_of_selector = function
    | Elem n -> spf "[%d]" n
    | Prop { prop; _ } -> spf ".%s" prop
    | Computed _ -> ".[computed]"
    | ObjRest _ -> "{ ... }"
    | ArrRest _ -> "[...]"
    | Default -> "<with default>"

  let rec string_of_binding = function
    | Root r -> string_of_root r
    | Select { selector; binding; _ } ->
      spf "(%s)%s" (string_of_binding binding) (string_of_selector selector)

  let string_of_import_kind =
    let open Ast.Statement.ImportDeclaration in
    function
    | ImportTypeof -> "typeof "
    | ImportType -> "type "
    | ImportValue -> ""

  let string_of_import = function
    | Named { kind; remote; local = _; remote_loc = _ } ->
      spf "%s%s" (Base.Option.value_map ~f:string_of_import_kind ~default:"" kind) remote
    | Namespace -> "namespace"
    | Default _ -> "default"

  let string_of_source = function
    | Binding b -> string_of_binding b
    | ChainExpression _ -> spf "heap"
    | RefiExpression _ -> spf "exp"
    | Update _ -> "[in/de]crement"
    | MemberAssign _ -> "member_assign"
    | OpAssign _ -> "opassign"
    | Function { function_ = { Ast.Function.id; _ }; _ } ->
      spf
        "fun %s"
        (Base.Option.value_map
           ~f:(fun (_, { Ast.Identifier.name; _ }) -> name)
           ~default:"<anonymous>"
           id
        )
    | DeclaredClass (_, { Ast.Statement.DeclareClass.id = (_, { Ast.Identifier.name; _ }); _ }) ->
      spf "declared class %s" name
    | Class { class_ = { Ast.Class.id; _ }; class_implicit_this_tparam = _; class_loc = _ } ->
      spf
        "class %s"
        (Base.Option.value_map
           ~f:(fun (_, { Ast.Identifier.name; _ }) -> name)
           ~default:"<anonymous>"
           id
        )
    | TypeAlias (_, { Ast.Statement.TypeAlias.right = (loc, _); _ }) ->
      spf "alias %s" (ALoc.debug_to_string loc)
    | OpaqueType (_, { Ast.Statement.OpaqueType.id = (loc, _); _ }) ->
      spf "opaque %s" (ALoc.debug_to_string loc)
    | TypeParam (_, (loc, _)) -> spf "tparam %s" (ALoc.debug_to_string loc)
    | Enum (loc, _) -> spf "enum %s" (ALoc.debug_to_string loc)
    | Interface _ -> "interface"
    | DeclaredModule _ -> "module"
    | GeneratorNext _ -> "next"
    | Import { import_kind; source; import; source_loc = _ } ->
      spf "import %s%s from %s" (string_of_import_kind import_kind) (string_of_import import) source
end

let default_of_binding = function
  | Root (Annotation { default_expression; _ })
  | Root (Contextual { default_expression; _ }) ->
    Base.Option.map default_expression ~f:(fun e -> DefaultExpr e)
  | Select { default; _ } -> default
  | Root _ -> None

module Destructure = struct
  open Ast.Pattern

  let type_of_pattern (_, p) =
    let open Ast.Pattern in
    match p with
    | Array { Array.annot = Ast.Type.Available t; _ }
    | Object { Object.annot = Ast.Type.Available t; _ }
    | Identifier { Identifier.annot = Ast.Type.Available t; _ } ->
      Some t
    | _ -> None

  let pattern_default acc = function
    | None -> acc
    | Some e ->
      let default =
        match default_of_binding acc with
        | Some default -> DefaultCons (e, default)
        | None -> DefaultExpr e
      in
      Select { selector = Default; default = Some default; binding = acc }

  let array_element acc i =
    let selector = Elem i in
    let default =
      acc
      |> default_of_binding
      |> Base.Option.map ~f:(fun default -> DefaultSelector (default, selector))
    in
    Select { selector; default; binding = acc }

  let array_rest_element acc i =
    let selector = ArrRest i in
    let default =
      acc
      |> default_of_binding
      |> Base.Option.map ~f:(fun default -> DefaultSelector (default, selector))
    in
    Select { selector; default; binding = acc }

  let object_named_property acc prop_loc x ~has_default =
    let selector = Prop { prop = x; prop_loc; has_default } in
    let default =
      acc
      |> default_of_binding
      |> Base.Option.map ~f:(fun default ->
             let d = DefaultSelector (default, selector) in
             if has_default then
               DefaultSelector (d, Default)
             else
               d
         )
    in
    Select { selector; default; binding = acc }

  let object_computed_property acc e =
    let selector = Computed e in
    let default =
      acc
      |> default_of_binding
      |> Base.Option.map ~f:(fun default -> DefaultSelector (default, selector))
    in
    Select { selector; default; binding = acc }

  let object_rest_property acc xs has_computed =
    let selector = ObjRest { used_props = xs; after_computed = has_computed } in
    let default =
      acc
      |> default_of_binding
      |> Base.Option.map ~f:(fun default -> DefaultSelector (default, selector))
    in
    Select { selector; default; binding = acc }

  let object_property acc xs key ~has_default =
    let open Ast.Pattern.Object in
    match key with
    | Property.Identifier (loc, { Ast.Identifier.name = x; comments = _ }) ->
      let acc = object_named_property acc loc x ~has_default in
      (acc, x :: xs, false)
    | Property.Literal (loc, { Ast.Literal.value = Ast.Literal.String x; _ }) ->
      let acc = object_named_property acc loc x ~has_default in
      (acc, x :: xs, false)
    | Property.Computed (_, { Ast.ComputedKey.expression; comments = _ }) ->
      let acc = object_computed_property acc expression in
      (acc, xs, true)
    | Property.Literal (_, _) -> (acc, xs, false)

  let identifier ~f acc (name_loc, { Ast.Identifier.name; _ }) =
    f name_loc (mk_reason (RIdentifier (OrdinaryName name)) name_loc) (Binding acc)

  let rec pattern ~f acc (_, p) =
    match p with
    | Array { Array.elements; annot = _; comments = _ } -> array_elements ~f acc elements
    | Object { Object.properties; annot = _; comments = _ } -> object_properties ~f acc properties
    | Identifier { Identifier.name = id; optional = _; annot = _ } -> identifier ~f acc id
    | Expression _ -> ()

  and array_elements ~f acc =
    let open Ast.Pattern.Array in
    Base.List.iteri ~f:(fun i -> function
      | Hole _ -> ()
      | Element (_, { Element.argument = p; default = d }) ->
        let acc = array_element acc i in
        let acc = pattern_default acc d in
        pattern ~f acc p
      | RestElement (_, { Ast.Pattern.RestElement.argument = p; comments = _ }) ->
        let acc = array_rest_element acc i in
        pattern ~f acc p
    )

  and object_properties =
    let open Ast.Pattern.Object in
    let prop ~f acc xs has_computed p =
      match p with
      | Property (_, { Property.key; pattern = p; default = d; shorthand = _ }) ->
        let has_default = d <> None in
        let (acc, xs, has_computed') = object_property acc xs key ~has_default in
        let acc = pattern_default acc d in
        pattern ~f acc p;
        (xs, has_computed || has_computed')
      | RestElement (_, { Ast.Pattern.RestElement.argument = p; comments = _ }) ->
        let acc = object_rest_property acc xs has_computed in
        pattern ~f acc p;
        (xs, false)
    in
    let rec loop ~f acc xs has_computed = function
      | [] -> ()
      | p :: ps ->
        let (xs, has_computed) = prop ~f acc xs has_computed p in
        loop ~f acc xs has_computed ps
    in
    (fun ~f acc ps -> loop ~f acc [] false ps)
end

let func_params_missing_annotations
    ~allow_this ((param_loc, { Ast.Function.Params.params; rest; this_; _ }) as all_params) body =
  let is_annotated p = p |> Destructure.type_of_pattern |> Base.Option.is_some in
  let params =
    Base.List.concat_map params ~f:(fun (_, { Ast.Function.Param.argument; _ }) ->
        if is_annotated argument then
          []
        else
          Flow_ast_utils.fold_bindings_of_pattern
            (fun acc (loc, { Ast.Identifier.name; _ }) ->
              mk_reason (RParameter (Some name)) loc :: acc)
            []
            argument
    )
  in
  let rest =
    Base.Option.value_map rest ~default:[] ~f:(fun (_, { Ast.Function.RestParam.argument; _ }) ->
        if is_annotated argument then
          []
        else
          Flow_ast_utils.fold_bindings_of_pattern
            (fun acc (loc, { Ast.Identifier.name; _ }) ->
              mk_reason (RRestParameter (Some name)) loc :: acc)
            []
            argument
    )
  in
  let this_ =
    if
      allow_this
      || (not @@ Signature_utils.This_finder.found_this_in_body_or_params body all_params)
      || Base.Option.is_some this_
    then
      []
    else
      [mk_reason (RImplicitThis (RFunction RNormal)) param_loc]
  in
  params @ rest @ this_

let func_missing_annotations ~allow_this ({ Ast.Function.return; generator; params; body; _ } as f)
    =
  let params = func_params_missing_annotations ~allow_this params body in
  match return with
  | Ast.Type.Available _ -> params
  | Ast.Type.Missing loc ->
    if (not (Nonvoid_return.might_have_nonvoid_return ALoc.none f)) && not generator then
      params
    else
      mk_reason RReturn loc :: params

let func_is_synthesizable_from_annotation ~allow_this ({ Ast.Function.predicate; _ } as f) =
  Base.Option.is_none predicate && Base.List.is_empty (func_missing_annotations ~allow_this f)

let def_of_function ~tparams_map ~hint ~has_this_def ~function_loc function_ =
  Function
    {
      hint;
      synthesizable_from_annotation =
        func_is_synthesizable_from_annotation ~allow_this:false function_;
      has_this_def;
      function_loc;
      function_;
      tparams_map;
    }

let func_scope_kind ?key { Ast.Function.async; generator; predicate; _ } =
  match (async, generator, predicate, key) with
  | ( false,
      false,
      None,
      Some
        (Ast.Expression.Object.Property.Identifier (_, { Ast.Identifier.name = "constructor"; _ }))
    ) ->
    Ctor
  | (true, true, None, _) -> AsyncGenerator
  | (true, false, None, _) -> Async
  | (false, true, None, _) -> Generator
  | (false, false, Some _, _) -> Predicate
  | (false, false, None, _) -> Ordinary
  | _ -> (* Invalid, default to ordinary and hopefully error elsewhere *) Ordinary

module Eq_test = Eq_test.Make (Scope_api.With_ALoc) (Ssa_api.With_ALoc) (Env_api.With_ALoc)

type map = (def * scope_kind * class_stack * ALoc.t virtual_reason) EnvMap.t

class def_finder env_entries providers toplevel_scope =
  object (this)
    inherit [map, ALoc.t] Flow_ast_visitor.visitor ~init:EnvMap.empty as super

    val mutable tparams : tparams_map = ALocMap.empty

    val mutable scope_kind : scope_kind = toplevel_scope

    val mutable class_stack : class_stack = []

    val mutable return_hint_stack : hint_node hint list = []

    method add_tparam loc name = tparams <- ALocMap.add loc name tparams

    method add_binding kind_and_loc reason src =
      this#update_acc (EnvMap.add kind_and_loc (src, scope_kind, class_stack, reason))

    method add_ordinary_binding loc = this#add_binding (Env_api.OrdinaryNameLoc, loc)

    method private in_scope : 'a 'b. ('a -> 'b) -> scope_kind -> 'a -> 'b =
      fun f scope' node ->
        let scope0 = scope_kind in
        scope_kind <- scope';
        let res = f node in
        scope_kind <- scope0;
        res

    method private in_new_tparams_env : 'a. ?keep:bool -> (unit -> 'a) -> 'a =
      fun ?(keep = false) f ->
        let old_tparams = tparams in
        if not keep then tparams <- ALocMap.empty;
        let result = f () in
        tparams <- old_tparams;
        result

    method! variable_declarator ~kind decl =
      let open Ast.Statement.VariableDeclaration.Declarator in
      let (_, { id; init }) = decl in
      let (source, hint) =
        match (Destructure.type_of_pattern id, init) with
        | (Some annot, _) ->
          ( Some
              (Annotation
                 {
                   tparams_map = ALocMap.empty;
                   optional = false;
                   default_expression = None;
                   is_assignment = true;
                   annot;
                 }
              ),
            Hint_t (AnnotationHint (ALocMap.empty, annot))
          )
        | (None, Some init) -> (Some (Value init), Hint_None)
        | (None, None) -> (None, Hint_None)
      in
      Base.Option.iter
        ~f:(fun acc -> Destructure.pattern ~f:this#add_ordinary_binding (Root acc) id)
        source;
      ignore @@ this#variable_declarator_pattern ~kind id;
      Base.Option.iter init ~f:(fun init ->
          this#visit_expression ~hint ~cond:NonConditionalContext init
      );
      decl

    method! declare_variable loc (decl : ('loc, 'loc) Ast.Statement.DeclareVariable.t) =
      let open Ast.Statement.DeclareVariable in
      let { id = (id_loc, { Ast.Identifier.name; _ }); annot; comments = _ } = decl in
      this#add_ordinary_binding
        id_loc
        (mk_reason (RIdentifier (OrdinaryName name)) id_loc)
        (Binding
           (Root
              (Annotation
                 {
                   tparams_map = ALocMap.empty;
                   optional = false;
                   default_expression = None;
                   is_assignment = true;
                   annot;
                 }
              )
           )
        );
      super#declare_variable loc decl

    method! function_param _ = failwith "Should be visited by visit_function_param"

    method private visit_function_param ~hint (param : ('loc, 'loc) Ast.Function.Param.t) =
      let open Ast.Function.Param in
      let (_, { argument; default = default_expression }) = param in
      let optional =
        match argument with
        | (_, Ast.Pattern.Identifier { Ast.Pattern.Identifier.optional; _ }) -> optional
        | _ -> false
      in
      let source =
        match Destructure.type_of_pattern argument with
        | Some annot ->
          Annotation
            { tparams_map = tparams; optional; default_expression; is_assignment = false; annot }
        | None ->
          let reason =
            match argument with
            | ( loc,
                Ast.Pattern.Identifier
                  { Ast.Pattern.Identifier.name = (_, { Ast.Identifier.name; _ }); _ }
              ) ->
              mk_reason (RParameter (Some name)) loc
            | (loc, _) -> mk_reason RDestructuring loc
          in
          Contextual { reason; hint; default_expression }
      in
      Destructure.pattern ~f:this#add_ordinary_binding (Root source) argument;
      ignore @@ super#function_param param

    method private visit_function_rest_param ~hint (expr : ('loc, 'loc) Ast.Function.RestParam.t) =
      let open Ast.Function.RestParam in
      let (_, { argument; comments = _ }) = expr in
      let source =
        match Destructure.type_of_pattern argument with
        | Some annot ->
          Annotation
            {
              tparams_map = tparams;
              optional = false;
              default_expression = None;
              is_assignment = false;
              annot;
            }
        | None ->
          let reason =
            match argument with
            | ( loc,
                Ast.Pattern.Identifier
                  { Ast.Pattern.Identifier.name = (_, { Ast.Identifier.name; _ }); _ }
              ) ->
              mk_reason (RRestParameter (Some name)) loc
            | (loc, _) ->
              (* TODO: This should be a parse error, but we only produce an internal
                 error in statement.ml. *)
              mk_reason (RCustom "contextual variable") loc
          in
          Contextual { reason; hint; default_expression = None }
      in
      Destructure.pattern ~f:this#add_ordinary_binding (Root source) argument;
      ignore @@ super#function_rest_param expr

    method! catch_clause_pattern pat =
      Destructure.pattern ~f:this#add_ordinary_binding (Root Catch) pat;
      super#catch_clause_pattern pat

    method private visit_function_expr ~func_hint ~has_this_def loc expr =
      let { Ast.Function.id; async; generator; sig_loc; _ } = expr in
      let scope_kind = func_scope_kind expr in
      this#in_new_tparams_env (fun () ->
          this#visit_function ~scope_kind ~func_hint expr;
          match id with
          | Some (id_loc, _) ->
            this#add_ordinary_binding
              id_loc
              (func_reason ~async ~generator sig_loc)
              (def_of_function
                 ~tparams_map:tparams
                 ~hint:func_hint
                 ~has_this_def
                 ~function_loc:loc
                 expr
              )
          | None -> ()
      )

    method! function_expression loc expr =
      this#visit_function_expr ~func_hint:Hint_None ~has_this_def:true loc expr;
      expr

    method! function_declaration loc expr =
      let { Ast.Function.id; async; generator; sig_loc; _ } = expr in
      let scope_kind = func_scope_kind expr in
      this#in_new_tparams_env (fun () ->
          this#visit_function ~func_hint:Hint_None ~scope_kind expr;
          match id with
          | Some (id_loc, _) ->
            this#add_ordinary_binding
              id_loc
              (func_reason ~async ~generator sig_loc)
              (def_of_function
                 ~tparams_map:tparams
                 ~hint:Hint_None
                 ~has_this_def:true
                 ~function_loc:loc
                 expr
              )
          | None -> ()
      );
      expr

    method! function_type loc ft =
      this#in_new_tparams_env ~keep:true (fun () -> super#function_type loc ft)

    method! function_ _ expr =
      let scope_kind = func_scope_kind expr in
      this#in_new_tparams_env (fun () -> this#visit_function ~scope_kind ~func_hint:Hint_None expr);
      expr

    method private visit_function ~scope_kind ~func_hint expr =
      this#in_scope
        (fun () ->
          let {
            Ast.Function.id = _;
            params = (_, { Ast.Function.Params.params = params_list; rest; comments = _; this_ = _ });
            body;
            async;
            generator;
            predicate;
            return;
            tparams = fun_tparams;
            sig_loc = _;
            comments = _;
          } =
            expr
          in
          Base.Option.iter fun_tparams ~f:(fun tparams -> ignore @@ this#type_params tparams);
          Base.List.iteri
            ~f:(fun i ->
              this#visit_function_param ~hint:(decompose_hint (Decomp_FuncParam i) func_hint))
            params_list;
          Base.Option.iter
            ~f:
              (this#visit_function_rest_param
                 ~hint:(decompose_hint (Decomp_FuncRest (List.length params_list)) func_hint)
              )
            rest;
          ignore @@ this#type_annotation_hint return;

          let return_hint =
            match return with
            | Ast.Type.Available annot -> Hint_t (AnnotationHint (ALocMap.empty, annot))
            | Ast.Type.Missing _ -> decompose_hint Decomp_FuncReturn func_hint
          in
          let old_stack = return_hint_stack in
          return_hint_stack <- return_hint :: return_hint_stack;
          let body_loc =
            match body with
            | Ast.Function.BodyBlock (loc, block) ->
              ignore @@ this#block loc block;
              loc
            | Ast.Function.BodyExpression ((loc, _) as expr) ->
              this#visit_expression ~hint:return_hint ~cond:NonConditionalContext expr;
              loc
          in
          return_hint_stack <- old_stack;

          begin
            if generator then
              let (loc, gen) =
                match return with
                | Ast.Type.Missing loc -> (loc, None)
                | Ast.Type.Available ((loc, _) as return_annot) ->
                  (loc, Some { tparams_map = tparams; return_annot; async })
              in

              this#add_ordinary_binding loc (mk_reason (RCustom "next") body_loc) (GeneratorNext gen)
          end;

          Base.Option.iter predicate ~f:(fun (_, { Ast.Type.Predicate.kind; comments = _ }) ->
              match kind with
              | Ast.Type.Predicate.Inferred -> ()
              | Ast.Type.Predicate.Declared expr ->
                this#visit_expression ~hint:Hint_None ~cond:NonConditionalContext expr
          ))
        scope_kind
        ()

    method! class_ loc expr =
      let open Ast.Class in
      let { id; body; tparams = class_tparams; extends; implements; class_decorators; comments = _ }
          =
        expr
      in
      this#in_new_tparams_env (fun () ->
          let old_stack = class_stack in
          class_stack <- loc :: class_stack;
          let class_implicit_this_tparam =
            this#in_scope
              (fun () ->
                Flow_ast_visitor.run_opt this#class_identifier id;
                Flow_ast_visitor.run_opt this#type_params class_tparams;
                let this_tparam_loc = Base.Option.value_map ~default:loc ~f:fst id in
                let class_tparams_map = tparams in
                this#add_tparam this_tparam_loc "this";
                ignore @@ this#class_body body;
                Flow_ast_visitor.run_opt (Flow_ast_mapper.map_loc this#class_extends) extends;
                Flow_ast_visitor.run_opt this#class_implements implements;
                Flow_ast_visitor.run_list this#class_decorator class_decorators;
                {
                  tparams_map = class_tparams_map;
                  class_tparams_loc = Base.Option.map ~f:fst class_tparams;
                })
              Ordinary
              ()
          in
          class_stack <- old_stack;
          begin
            match id with
            | Some (id_loc, { Ast.Identifier.name; _ }) ->
              let name = OrdinaryName name in
              let reason = mk_reason (RType name) id_loc in
              this#add_ordinary_binding
                id_loc
                reason
                (Class { class_loc = loc; class_implicit_this_tparam; class_ = expr })
            | None ->
              let reason = mk_reason (RType (OrdinaryName "<<anonymous class>>")) loc in
              this#add_ordinary_binding
                loc
                reason
                (Class { class_loc = loc; class_implicit_this_tparam; class_ = expr })
          end;
          expr
      )

    method! class_property _loc (prop : ('loc, 'loc) Ast.Class.Property.t') =
      let open Ast.Class.Property in
      let { key; value; annot; static = _; variance; comments = _ } = prop in
      ignore @@ this#object_key key;
      ignore @@ this#type_annotation_hint annot;
      let hint =
        match annot with
        | Ast.Type.Available annot -> Hint_t (AnnotationHint (ALocMap.empty, annot))
        | Ast.Type.Missing _ -> Hint_None
      in
      this#visit_class_property_value ~hint value;
      ignore @@ this#variance_opt variance;
      prop

    method! class_private_field _loc (prop : ('loc, 'loc) Ast.Class.PrivateField.t') =
      let open Ast.Class.PrivateField in
      let { key; value; annot; static = _; variance; comments = _ } = prop in
      ignore @@ this#private_name key;
      ignore @@ this#type_annotation_hint annot;
      let hint =
        match annot with
        | Ast.Type.Available annot -> Hint_t (AnnotationHint (ALocMap.empty, annot))
        | Ast.Type.Missing _ -> Hint_None
      in
      this#visit_class_property_value ~hint value;
      ignore @@ this#variance_opt variance;
      prop

    method private visit_class_property_value ~hint value =
      let open Ast.Class.Property in
      match value with
      | Declared -> ()
      | Uninitialized -> ()
      | Initialized x -> this#visit_expression ~cond:NonConditionalContext ~hint x

    method! class_method _loc (meth : ('loc, 'loc) Ast.Class.Method.t') =
      let open Ast.Class.Method in
      let { kind = _; key; value = (_, value); static = _; decorators; comments = _ } = meth in
      let _ = this#object_key key in
      let scope_kind = func_scope_kind ~key value in
      let () =
        this#in_new_tparams_env ~keep:true (fun () ->
            this#visit_function ~scope_kind ~func_hint:Hint_None value
        )
      in
      let (_ : _ list) = map_list this#class_decorator decorators in
      meth

    method! declare_function loc (decl : ('loc, 'loc) Ast.Statement.DeclareFunction.t) =
      match Declare_function_utils.declare_function_to_function_declaration_simple loc decl with
      | Some stmt ->
        let _ = this#statement (loc, stmt) in
        decl
      | None ->
        let open Ast.Statement.DeclareFunction in
        let { id = (id_loc, _); annot; predicate = _; comments = _ } = decl in
        this#add_ordinary_binding
          id_loc
          (func_reason ~async:false ~generator:false loc)
          (Binding
             (Root
                (Annotation
                   {
                     tparams_map = ALocMap.empty;
                     optional = false;
                     default_expression = None;
                     is_assignment = false;
                     annot;
                   }
                )
             )
          );
        super#declare_function loc decl

    method! declare_class loc (decl : ('loc, 'loc) Ast.Statement.DeclareClass.t) =
      let open Ast.Statement.DeclareClass in
      let { id = (id_loc, { Ast.Identifier.name; _ }); _ } = decl in
      this#add_ordinary_binding
        id_loc
        (mk_reason (RClass (RIdentifier (OrdinaryName name))) loc)
        (DeclaredClass (loc, decl));
      super#declare_class loc decl

    method! assignment loc (expr : ('loc, 'loc) Ast.Expression.Assignment.t) =
      let open Ast.Expression.Assignment in
      let { operator; left = (lhs_loc, lhs_node) as left; right; comments = _ } = expr in
      let () =
        match (operator, lhs_node) with
        | (None, Ast.Pattern.Expression (member_loc, Ast.Expression.Member member)) ->
          (* Use super member to visit sub-expressions to avoid record a read of the member. *)
          ignore @@ super#member member_loc member;
          this#add_ordinary_binding
            member_loc
            (mk_pattern_reason left)
            (MemberAssign { member_loc; member; rhs = right })
        | (None, _) -> Destructure.pattern ~f:this#add_ordinary_binding (Root (Value right)) left
        | ( Some operator,
            Ast.Pattern.Identifier
              { Ast.Pattern.Identifier.name = (id_loc, { Ast.Identifier.name; _ }); _ }
          ) ->
          this#add_ordinary_binding
            id_loc
            (mk_reason (RIdentifier (OrdinaryName name)) id_loc)
            (OpAssign { exp_loc = loc; lhs = left; op = operator; rhs = right })
        | (Some operator, Ast.Pattern.Expression ((def_loc, _) as e)) ->
          (* In op_assign, the LHS will also be read. *)
          let cond =
            match operator with
            | AndAssign
            | OrAssign ->
              OtherConditionalTest
            | _ -> NonConditionalContext
          in
          this#visit_expression ~cond ~hint:Hint_None e;
          this#add_ordinary_binding
            def_loc
            (mk_pattern_reason left)
            (OpAssign { exp_loc = loc; lhs = left; op = operator; rhs = right })
        | _ -> ()
      in
      let is_provider =
        Flow_ast_utils.fold_bindings_of_pattern
          (fun acc (loc, _) -> acc || Env_api.Provider_api.is_provider providers loc)
          false
          left
      in
      let hint =
        if is_provider then
          Hint_None
        else
          match lhs_node with
          | Ast.Pattern.Identifier _ ->
            Env_api.Provider_api.providers_of_def providers lhs_loc
            |> Base.Option.value_map ~f:(fun x -> x.Env_api.Provider_api.providers) ~default:[]
            |> Base.List.map ~f:(fun { Env_api.Provider_api.reason; _ } ->
                   Reason.aloc_of_reason reason
               )
            |> Nel.of_list
            |> Base.Option.value_map ~default:Hint_None ~f:(fun providers ->
                   Hint_t (ProvidersHint providers)
               )
          | Ast.Pattern.Expression
              (_, Ast.Expression.Member { Ast.Expression.Member._object; property; comments = _ })
            ->
            (match property with
            | Ast.Expression.Member.PropertyIdentifier (_, { Ast.Identifier.name; comments = _ }) ->
              decompose_hint (Decomp_ObjProp name) (Hint_t (ValueHint _object))
            | Ast.Expression.Member.PropertyPrivateName _ ->
              (* TODO create a hint based on the current class. *)
              Hint_None
            | Ast.Expression.Member.PropertyExpression _ ->
              decompose_hint Decomp_ObjComputed (Hint_t (ValueHint _object)))
          | _ ->
            (* TODO create a hint based on the lhs pattern *)
            Hint_t (AnnotationHint (ALocMap.empty, (lhs_loc, (lhs_loc, Ast.Type.Any None))))
      in
      this#visit_expression ~hint ~cond:NonConditionalContext right;
      expr

    method! update_expression loc (expr : (ALoc.t, ALoc.t) Ast.Expression.Update.t) =
      let open Ast.Expression.Update in
      let { argument; operator; prefix = _; comments = _ } = expr in
      begin
        match argument with
        | (_, Ast.Expression.Identifier (id_loc, { Ast.Identifier.name; _ })) -><commit_msg>Use Hint_placeholder for assigmments in name-def<commit_after>(*
 * Copyright (c) Meta Platforms, Inc. and affiliates.
 *
 * This source code is licensed under the MIT license found in the
 * LICENSE file in the root directory of this source tree.
 *)

module Ast = Flow_ast
open Hint_api
open Reason
open Flow_ast_mapper
open Loc_collections
module EnvMap = Env_api.EnvMap

type cond_context =
  | NonConditionalContext
  | SwitchConditionalTest of {
      case_test_reason: reason;
      switch_discriminant_reason: reason;
    }
  | OtherConditionalTest

type scope_kind =
  | Ordinary (* function or module *)
  | Async (* async function *)
  | Generator (* generator function *)
  | AsyncGenerator (* async generator function *)
  | Module (* module scope *)
  | Predicate (* predicate function *)
  | Ctor

type class_stack = ALoc.t list

type for_kind =
  | In
  | Of of { await: bool }

(* A map from location of tparam to name. *)
type tparams_map = string ALocMap.t

type hint_node =
  | AnnotationHint of tparams_map * (ALoc.t, ALoc.t) Ast.Type.annotation
  | ValueHint of (ALoc.t, ALoc.t) Ast.Expression.t
  | ProvidersHint of ALoc.t Nel.t

type root =
  | Annotation of {
      tparams_map: tparams_map;
      optional: bool;
      default_expression: (ALoc.t, ALoc.t) Ast.Expression.t option;
      is_assignment: bool;
      annot: (ALoc.t, ALoc.t) Ast.Type.annotation;
    }
  | Value of (ALoc.t, ALoc.t) Ast.Expression.t
  | Contextual of {
      reason: Reason.reason;
      hint: hint_node hint;
      default_expression: (ALoc.t, ALoc.t) Ast.Expression.t option;
    }
  | Catch
  | For of for_kind * (ALoc.t, ALoc.t) Ast.Expression.t

type selector =
  | Elem of int
  | Prop of {
      prop: string;
      prop_loc: ALoc.t;
      has_default: bool;
    }
  | Computed of (ALoc.t, ALoc.t) Ast.Expression.t
  | ObjRest of {
      used_props: string list;
      after_computed: bool;
    }
  | ArrRest of int
  | Default

type default =
  | DefaultExpr of (ALoc.t, ALoc.t) Ast.Expression.t
  | DefaultCons of (ALoc.t, ALoc.t) Ast.Expression.t * default
  | DefaultSelector of default * selector

type binding =
  | Root of root
  | Select of {
      selector: selector;
      default: default option;
      binding: binding;
    }

type import =
  | Named of {
      kind: Ast.Statement.ImportDeclaration.import_kind option;
      remote: string;
      remote_loc: ALoc.t;
      local: string;
    }
  | Namespace
  | Default of string

type generator_annot = {
  tparams_map: tparams_map;
  return_annot: (ALoc.t, ALoc.t) Ast.Type.annotation;
  async: bool;
}

type class_implicit_this_tparam = {
  tparams_map: tparams_map;
  class_tparams_loc: ALoc.t option;
}

type def =
  | Binding of binding
  | ChainExpression of cond_context * (ALoc.t, ALoc.t) Ast.Expression.t
  | RefiExpression of (ALoc.t, ALoc.t) Ast.Expression.t
  | MemberAssign of {
      member_loc: ALoc.t;
      member: (ALoc.t, ALoc.t) Ast.Expression.Member.t;
      rhs: (ALoc.t, ALoc.t) Ast.Expression.t;
    }
  | OpAssign of {
      exp_loc: ALoc.t;
      lhs: (ALoc.t, ALoc.t) Ast.Pattern.t;
      op: Ast.Expression.Assignment.operator;
      rhs: (ALoc.t, ALoc.t) Ast.Expression.t;
    }
  | Update of {
      exp_loc: ALoc.t;
      lhs_member: (ALoc.t, ALoc.t) Ast.Expression.t option;
      op: Ast.Expression.Update.operator;
    }
  | Function of {
      hint: hint_node hint;
      synthesizable_from_annotation: bool;
      has_this_def: bool;
      function_loc: ALoc.t;
      function_: (ALoc.t, ALoc.t) Ast.Function.t;
      tparams_map: tparams_map;
    }
  | Class of {
      class_implicit_this_tparam: class_implicit_this_tparam;
      class_: (ALoc.t, ALoc.t) Ast.Class.t;
      class_loc: ALoc.t;
    }
  | DeclaredClass of ALoc.t * (ALoc.t, ALoc.t) Ast.Statement.DeclareClass.t
  | TypeAlias of ALoc.t * (ALoc.t, ALoc.t) Ast.Statement.TypeAlias.t
  | OpaqueType of ALoc.t * (ALoc.t, ALoc.t) Ast.Statement.OpaqueType.t
  | TypeParam of tparams_map * (ALoc.t, ALoc.t) Ast.Type.TypeParam.t
  | Interface of ALoc.t * (ALoc.t, ALoc.t) Ast.Statement.Interface.t
  | Enum of ALoc.t * ALoc.t Ast.Statement.EnumDeclaration.body
  | Import of {
      import_kind: Ast.Statement.ImportDeclaration.import_kind;
      import: import;
      source: string;
      source_loc: ALoc.t;
    }
  | GeneratorNext of generator_annot option
  | DeclaredModule of ALoc.t * (ALoc.t, ALoc.t) Ast.Statement.DeclareModule.t

module Print = struct
  open Utils_js

  let string_of_root = function
    | Contextual _ -> "contextual"
    | Catch -> "catch"
    | Annotation { annot = (loc, _); _ } -> spf "annot %s" (ALoc.debug_to_string loc)
    | Value (loc, _) -> spf "val %s" (ALoc.debug_to_string loc)
    | For (In, (loc, _)) -> spf "for in %s" (ALoc.debug_to_string loc)
    | For (Of _, (loc, _)) -> spf "for of %s" (ALoc.debug_to_string loc)

  let string_of_selector = function
    | Elem n -> spf "[%d]" n
    | Prop { prop; _ } -> spf ".%s" prop
    | Computed _ -> ".[computed]"
    | ObjRest _ -> "{ ... }"
    | ArrRest _ -> "[...]"
    | Default -> "<with default>"

  let rec string_of_binding = function
    | Root r -> string_of_root r
    | Select { selector; binding; _ } ->
      spf "(%s)%s" (string_of_binding binding) (string_of_selector selector)

  let string_of_import_kind =
    let open Ast.Statement.ImportDeclaration in
    function
    | ImportTypeof -> "typeof "
    | ImportType -> "type "
    | ImportValue -> ""

  let string_of_import = function
    | Named { kind; remote; local = _; remote_loc = _ } ->
      spf "%s%s" (Base.Option.value_map ~f:string_of_import_kind ~default:"" kind) remote
    | Namespace -> "namespace"
    | Default _ -> "default"

  let string_of_source = function
    | Binding b -> string_of_binding b
    | ChainExpression _ -> spf "heap"
    | RefiExpression _ -> spf "exp"
    | Update _ -> "[in/de]crement"
    | MemberAssign _ -> "member_assign"
    | OpAssign _ -> "opassign"
    | Function { function_ = { Ast.Function.id; _ }; _ } ->
      spf
        "fun %s"
        (Base.Option.value_map
           ~f:(fun (_, { Ast.Identifier.name; _ }) -> name)
           ~default:"<anonymous>"
           id
        )
    | DeclaredClass (_, { Ast.Statement.DeclareClass.id = (_, { Ast.Identifier.name; _ }); _ }) ->
      spf "declared class %s" name
    | Class { class_ = { Ast.Class.id; _ }; class_implicit_this_tparam = _; class_loc = _ } ->
      spf
        "class %s"
        (Base.Option.value_map
           ~f:(fun (_, { Ast.Identifier.name; _ }) -> name)
           ~default:"<anonymous>"
           id
        )
    | TypeAlias (_, { Ast.Statement.TypeAlias.right = (loc, _); _ }) ->
      spf "alias %s" (ALoc.debug_to_string loc)
    | OpaqueType (_, { Ast.Statement.OpaqueType.id = (loc, _); _ }) ->
      spf "opaque %s" (ALoc.debug_to_string loc)
    | TypeParam (_, (loc, _)) -> spf "tparam %s" (ALoc.debug_to_string loc)
    | Enum (loc, _) -> spf "enum %s" (ALoc.debug_to_string loc)
    | Interface _ -> "interface"
    | DeclaredModule _ -> "module"
    | GeneratorNext _ -> "next"
    | Import { import_kind; source; import; source_loc = _ } ->
      spf "import %s%s from %s" (string_of_import_kind import_kind) (string_of_import import) source
end

let default_of_binding = function
  | Root (Annotation { default_expression; _ })
  | Root (Contextual { default_expression; _ }) ->
    Base.Option.map default_expression ~f:(fun e -> DefaultExpr e)
  | Select { default; _ } -> default
  | Root _ -> None

module Destructure = struct
  open Ast.Pattern

  let type_of_pattern (_, p) =
    let open Ast.Pattern in
    match p with
    | Array { Array.annot = Ast.Type.Available t; _ }
    | Object { Object.annot = Ast.Type.Available t; _ }
    | Identifier { Identifier.annot = Ast.Type.Available t; _ } ->
      Some t
    | _ -> None

  let pattern_default acc = function
    | None -> acc
    | Some e ->
      let default =
        match default_of_binding acc with
        | Some default -> DefaultCons (e, default)
        | None -> DefaultExpr e
      in
      Select { selector = Default; default = Some default; binding = acc }

  let array_element acc i =
    let selector = Elem i in
    let default =
      acc
      |> default_of_binding
      |> Base.Option.map ~f:(fun default -> DefaultSelector (default, selector))
    in
    Select { selector; default; binding = acc }

  let array_rest_element acc i =
    let selector = ArrRest i in
    let default =
      acc
      |> default_of_binding
      |> Base.Option.map ~f:(fun default -> DefaultSelector (default, selector))
    in
    Select { selector; default; binding = acc }

  let object_named_property acc prop_loc x ~has_default =
    let selector = Prop { prop = x; prop_loc; has_default } in
    let default =
      acc
      |> default_of_binding
      |> Base.Option.map ~f:(fun default ->
             let d = DefaultSelector (default, selector) in
             if has_default then
               DefaultSelector (d, Default)
             else
               d
         )
    in
    Select { selector; default; binding = acc }

  let object_computed_property acc e =
    let selector = Computed e in
    let default =
      acc
      |> default_of_binding
      |> Base.Option.map ~f:(fun default -> DefaultSelector (default, selector))
    in
    Select { selector; default; binding = acc }

  let object_rest_property acc xs has_computed =
    let selector = ObjRest { used_props = xs; after_computed = has_computed } in
    let default =
      acc
      |> default_of_binding
      |> Base.Option.map ~f:(fun default -> DefaultSelector (default, selector))
    in
    Select { selector; default; binding = acc }

  let object_property acc xs key ~has_default =
    let open Ast.Pattern.Object in
    match key with
    | Property.Identifier (loc, { Ast.Identifier.name = x; comments = _ }) ->
      let acc = object_named_property acc loc x ~has_default in
      (acc, x :: xs, false)
    | Property.Literal (loc, { Ast.Literal.value = Ast.Literal.String x; _ }) ->
      let acc = object_named_property acc loc x ~has_default in
      (acc, x :: xs, false)
    | Property.Computed (_, { Ast.ComputedKey.expression; comments = _ }) ->
      let acc = object_computed_property acc expression in
      (acc, xs, true)
    | Property.Literal (_, _) -> (acc, xs, false)

  let identifier ~f acc (name_loc, { Ast.Identifier.name; _ }) =
    f name_loc (mk_reason (RIdentifier (OrdinaryName name)) name_loc) (Binding acc)

  let rec pattern ~f acc (_, p) =
    match p with
    | Array { Array.elements; annot = _; comments = _ } -> array_elements ~f acc elements
    | Object { Object.properties; annot = _; comments = _ } -> object_properties ~f acc properties
    | Identifier { Identifier.name = id; optional = _; annot = _ } -> identifier ~f acc id
    | Expression _ -> ()

  and array_elements ~f acc =
    let open Ast.Pattern.Array in
    Base.List.iteri ~f:(fun i -> function
      | Hole _ -> ()
      | Element (_, { Element.argument = p; default = d }) ->
        let acc = array_element acc i in
        let acc = pattern_default acc d in
        pattern ~f acc p
      | RestElement (_, { Ast.Pattern.RestElement.argument = p; comments = _ }) ->
        let acc = array_rest_element acc i in
        pattern ~f acc p
    )

  and object_properties =
    let open Ast.Pattern.Object in
    let prop ~f acc xs has_computed p =
      match p with
      | Property (_, { Property.key; pattern = p; default = d; shorthand = _ }) ->
        let has_default = d <> None in
        let (acc, xs, has_computed') = object_property acc xs key ~has_default in
        let acc = pattern_default acc d in
        pattern ~f acc p;
        (xs, has_computed || has_computed')
      | RestElement (_, { Ast.Pattern.RestElement.argument = p; comments = _ }) ->
        let acc = object_rest_property acc xs has_computed in
        pattern ~f acc p;
        (xs, false)
    in
    let rec loop ~f acc xs has_computed = function
      | [] -> ()
      | p :: ps ->
        let (xs, has_computed) = prop ~f acc xs has_computed p in
        loop ~f acc xs has_computed ps
    in
    (fun ~f acc ps -> loop ~f acc [] false ps)
end

let func_params_missing_annotations
    ~allow_this ((param_loc, { Ast.Function.Params.params; rest; this_; _ }) as all_params) body =
  let is_annotated p = p |> Destructure.type_of_pattern |> Base.Option.is_some in
  let params =
    Base.List.concat_map params ~f:(fun (_, { Ast.Function.Param.argument; _ }) ->
        if is_annotated argument then
          []
        else
          Flow_ast_utils.fold_bindings_of_pattern
            (fun acc (loc, { Ast.Identifier.name; _ }) ->
              mk_reason (RParameter (Some name)) loc :: acc)
            []
            argument
    )
  in
  let rest =
    Base.Option.value_map rest ~default:[] ~f:(fun (_, { Ast.Function.RestParam.argument; _ }) ->
        if is_annotated argument then
          []
        else
          Flow_ast_utils.fold_bindings_of_pattern
            (fun acc (loc, { Ast.Identifier.name; _ }) ->
              mk_reason (RRestParameter (Some name)) loc :: acc)
            []
            argument
    )
  in
  let this_ =
    if
      allow_this
      || (not @@ Signature_utils.This_finder.found_this_in_body_or_params body all_params)
      || Base.Option.is_some this_
    then
      []
    else
      [mk_reason (RImplicitThis (RFunction RNormal)) param_loc]
  in
  params @ rest @ this_

let func_missing_annotations ~allow_this ({ Ast.Function.return; generator; params; body; _ } as f)
    =
  let params = func_params_missing_annotations ~allow_this params body in
  match return with
  | Ast.Type.Available _ -> params
  | Ast.Type.Missing loc ->
    if (not (Nonvoid_return.might_have_nonvoid_return ALoc.none f)) && not generator then
      params
    else
      mk_reason RReturn loc :: params

let func_is_synthesizable_from_annotation ~allow_this ({ Ast.Function.predicate; _ } as f) =
  Base.Option.is_none predicate && Base.List.is_empty (func_missing_annotations ~allow_this f)

let def_of_function ~tparams_map ~hint ~has_this_def ~function_loc function_ =
  Function
    {
      hint;
      synthesizable_from_annotation =
        func_is_synthesizable_from_annotation ~allow_this:false function_;
      has_this_def;
      function_loc;
      function_;
      tparams_map;
    }

let func_scope_kind ?key { Ast.Function.async; generator; predicate; _ } =
  match (async, generator, predicate, key) with
  | ( false,
      false,
      None,
      Some
        (Ast.Expression.Object.Property.Identifier (_, { Ast.Identifier.name = "constructor"; _ }))
    ) ->
    Ctor
  | (true, true, None, _) -> AsyncGenerator
  | (true, false, None, _) -> Async
  | (false, true, None, _) -> Generator
  | (false, false, Some _, _) -> Predicate
  | (false, false, None, _) -> Ordinary
  | _ -> (* Invalid, default to ordinary and hopefully error elsewhere *) Ordinary

module Eq_test = Eq_test.Make (Scope_api.With_ALoc) (Ssa_api.With_ALoc) (Env_api.With_ALoc)

type map = (def * scope_kind * class_stack * ALoc.t virtual_reason) EnvMap.t

class def_finder env_entries providers toplevel_scope =
  object (this)
    inherit [map, ALoc.t] Flow_ast_visitor.visitor ~init:EnvMap.empty as super

    val mutable tparams : tparams_map = ALocMap.empty

    val mutable scope_kind : scope_kind = toplevel_scope

    val mutable class_stack : class_stack = []

    val mutable return_hint_stack : hint_node hint list = []

    method add_tparam loc name = tparams <- ALocMap.add loc name tparams

    method add_binding kind_and_loc reason src =
      this#update_acc (EnvMap.add kind_and_loc (src, scope_kind, class_stack, reason))

    method add_ordinary_binding loc = this#add_binding (Env_api.OrdinaryNameLoc, loc)

    method private in_scope : 'a 'b. ('a -> 'b) -> scope_kind -> 'a -> 'b =
      fun f scope' node ->
        let scope0 = scope_kind in
        scope_kind <- scope';
        let res = f node in
        scope_kind <- scope0;
        res

    method private in_new_tparams_env : 'a. ?keep:bool -> (unit -> 'a) -> 'a =
      fun ?(keep = false) f ->
        let old_tparams = tparams in
        if not keep then tparams <- ALocMap.empty;
        let result = f () in
        tparams <- old_tparams;
        result

    method! variable_declarator ~kind decl =
      let open Ast.Statement.VariableDeclaration.Declarator in
      let (_, { id; init }) = decl in
      let (source, hint) =
        match (Destructure.type_of_pattern id, init) with
        | (Some annot, _) ->
          ( Some
              (Annotation
                 {
                   tparams_map = ALocMap.empty;
                   optional = false;
                   default_expression = None;
                   is_assignment = true;
                   annot;
                 }
              ),
            Hint_t (AnnotationHint (ALocMap.empty, annot))
          )
        | (None, Some init) -> (Some (Value init), Hint_None)
        | (None, None) -> (None, Hint_None)
      in
      Base.Option.iter
        ~f:(fun acc -> Destructure.pattern ~f:this#add_ordinary_binding (Root acc) id)
        source;
      ignore @@ this#variable_declarator_pattern ~kind id;
      Base.Option.iter init ~f:(fun init ->
          this#visit_expression ~hint ~cond:NonConditionalContext init
      );
      decl

    method! declare_variable loc (decl : ('loc, 'loc) Ast.Statement.DeclareVariable.t) =
      let open Ast.Statement.DeclareVariable in
      let { id = (id_loc, { Ast.Identifier.name; _ }); annot; comments = _ } = decl in
      this#add_ordinary_binding
        id_loc
        (mk_reason (RIdentifier (OrdinaryName name)) id_loc)
        (Binding
           (Root
              (Annotation
                 {
                   tparams_map = ALocMap.empty;
                   optional = false;
                   default_expression = None;
                   is_assignment = true;
                   annot;
                 }
              )
           )
        );
      super#declare_variable loc decl

    method! function_param _ = failwith "Should be visited by visit_function_param"

    method private visit_function_param ~hint (param : ('loc, 'loc) Ast.Function.Param.t) =
      let open Ast.Function.Param in
      let (_, { argument; default = default_expression }) = param in
      let optional =
        match argument with
        | (_, Ast.Pattern.Identifier { Ast.Pattern.Identifier.optional; _ }) -> optional
        | _ -> false
      in
      let source =
        match Destructure.type_of_pattern argument with
        | Some annot ->
          Annotation
            { tparams_map = tparams; optional; default_expression; is_assignment = false; annot }
        | None ->
          let reason =
            match argument with
            | ( loc,
                Ast.Pattern.Identifier
                  { Ast.Pattern.Identifier.name = (_, { Ast.Identifier.name; _ }); _ }
              ) ->
              mk_reason (RParameter (Some name)) loc
            | (loc, _) -> mk_reason RDestructuring loc
          in
          Contextual { reason; hint; default_expression }
      in
      Destructure.pattern ~f:this#add_ordinary_binding (Root source) argument;
      ignore @@ super#function_param param

    method private visit_function_rest_param ~hint (expr : ('loc, 'loc) Ast.Function.RestParam.t) =
      let open Ast.Function.RestParam in
      let (_, { argument; comments = _ }) = expr in
      let source =
        match Destructure.type_of_pattern argument with
        | Some annot ->
          Annotation
            {
              tparams_map = tparams;
              optional = false;
              default_expression = None;
              is_assignment = false;
              annot;
            }
        | None ->
          let reason =
            match argument with
            | ( loc,
                Ast.Pattern.Identifier
                  { Ast.Pattern.Identifier.name = (_, { Ast.Identifier.name; _ }); _ }
              ) ->
              mk_reason (RRestParameter (Some name)) loc
            | (loc, _) ->
              (* TODO: This should be a parse error, but we only produce an internal
                 error in statement.ml. *)
              mk_reason (RCustom "contextual variable") loc
          in
          Contextual { reason; hint; default_expression = None }
      in
      Destructure.pattern ~f:this#add_ordinary_binding (Root source) argument;
      ignore @@ super#function_rest_param expr

    method! catch_clause_pattern pat =
      Destructure.pattern ~f:this#add_ordinary_binding (Root Catch) pat;
      super#catch_clause_pattern pat

    method private visit_function_expr ~func_hint ~has_this_def loc expr =
      let { Ast.Function.id; async; generator; sig_loc; _ } = expr in
      let scope_kind = func_scope_kind expr in
      this#in_new_tparams_env (fun () ->
          this#visit_function ~scope_kind ~func_hint expr;
          match id with
          | Some (id_loc, _) ->
            this#add_ordinary_binding
              id_loc
              (func_reason ~async ~generator sig_loc)
              (def_of_function
                 ~tparams_map:tparams
                 ~hint:func_hint
                 ~has_this_def
                 ~function_loc:loc
                 expr
              )
          | None -> ()
      )

    method! function_expression loc expr =
      this#visit_function_expr ~func_hint:Hint_None ~has_this_def:true loc expr;
      expr

    method! function_declaration loc expr =
      let { Ast.Function.id; async; generator; sig_loc; _ } = expr in
      let scope_kind = func_scope_kind expr in
      this#in_new_tparams_env (fun () ->
          this#visit_function ~func_hint:Hint_None ~scope_kind expr;
          match id with
          | Some (id_loc, _) ->
            this#add_ordinary_binding
              id_loc
              (func_reason ~async ~generator sig_loc)
              (def_of_function
                 ~tparams_map:tparams
                 ~hint:Hint_None
                 ~has_this_def:true
                 ~function_loc:loc
                 expr
              )
          | None -> ()
      );
      expr

    method! function_type loc ft =
      this#in_new_tparams_env ~keep:true (fun () -> super#function_type loc ft)

    method! function_ _ expr =
      let scope_kind = func_scope_kind expr in
      this#in_new_tparams_env (fun () -> this#visit_function ~scope_kind ~func_hint:Hint_None expr);
      expr

    method private visit_function ~scope_kind ~func_hint expr =
      this#in_scope
        (fun () ->
          let {
            Ast.Function.id = _;
            params = (_, { Ast.Function.Params.params = params_list; rest; comments = _; this_ = _ });
            body;
            async;
            generator;
            predicate;
            return;
            tparams = fun_tparams;
            sig_loc = _;
            comments = _;
          } =
            expr
          in
          Base.Option.iter fun_tparams ~f:(fun tparams -> ignore @@ this#type_params tparams);
          Base.List.iteri
            ~f:(fun i ->
              this#visit_function_param ~hint:(decompose_hint (Decomp_FuncParam i) func_hint))
            params_list;
          Base.Option.iter
            ~f:
              (this#visit_function_rest_param
                 ~hint:(decompose_hint (Decomp_FuncRest (List.length params_list)) func_hint)
              )
            rest;
          ignore @@ this#type_annotation_hint return;

          let return_hint =
            match return with
            | Ast.Type.Available annot -> Hint_t (AnnotationHint (ALocMap.empty, annot))
            | Ast.Type.Missing _ -> decompose_hint Decomp_FuncReturn func_hint
          in
          let old_stack = return_hint_stack in
          return_hint_stack <- return_hint :: return_hint_stack;
          let body_loc =
            match body with
            | Ast.Function.BodyBlock (loc, block) ->
              ignore @@ this#block loc block;
              loc
            | Ast.Function.BodyExpression ((loc, _) as expr) ->
              this#visit_expression ~hint:return_hint ~cond:NonConditionalContext expr;
              loc
          in
          return_hint_stack <- old_stack;

          begin
            if generator then
              let (loc, gen) =
                match return with
                | Ast.Type.Missing loc -> (loc, None)
                | Ast.Type.Available ((loc, _) as return_annot) ->
                  (loc, Some { tparams_map = tparams; return_annot; async })
              in

              this#add_ordinary_binding loc (mk_reason (RCustom "next") body_loc) (GeneratorNext gen)
          end;

          Base.Option.iter predicate ~f:(fun (_, { Ast.Type.Predicate.kind; comments = _ }) ->
              match kind with
              | Ast.Type.Predicate.Inferred -> ()
              | Ast.Type.Predicate.Declared expr ->
                this#visit_expression ~hint:Hint_None ~cond:NonConditionalContext expr
          ))
        scope_kind
        ()

    method! class_ loc expr =
      let open Ast.Class in
      let { id; body; tparams = class_tparams; extends; implements; class_decorators; comments = _ }
          =
        expr
      in
      this#in_new_tparams_env (fun () ->
          let old_stack = class_stack in
          class_stack <- loc :: class_stack;
          let class_implicit_this_tparam =
            this#in_scope
              (fun () ->
                Flow_ast_visitor.run_opt this#class_identifier id;
                Flow_ast_visitor.run_opt this#type_params class_tparams;
                let this_tparam_loc = Base.Option.value_map ~default:loc ~f:fst id in
                let class_tparams_map = tparams in
                this#add_tparam this_tparam_loc "this";
                ignore @@ this#class_body body;
                Flow_ast_visitor.run_opt (Flow_ast_mapper.map_loc this#class_extends) extends;
                Flow_ast_visitor.run_opt this#class_implements implements;
                Flow_ast_visitor.run_list this#class_decorator class_decorators;
                {
                  tparams_map = class_tparams_map;
                  class_tparams_loc = Base.Option.map ~f:fst class_tparams;
                })
              Ordinary
              ()
          in
          class_stack <- old_stack;
          begin
            match id with
            | Some (id_loc, { Ast.Identifier.name; _ }) ->
              let name = OrdinaryName name in
              let reason = mk_reason (RType name) id_loc in
              this#add_ordinary_binding
                id_loc
                reason
                (Class { class_loc = loc; class_implicit_this_tparam; class_ = expr })
            | None ->
              let reason = mk_reason (RType (OrdinaryName "<<anonymous class>>")) loc in
              this#add_ordinary_binding
                loc
                reason
                (Class { class_loc = loc; class_implicit_this_tparam; class_ = expr })
          end;
          expr
      )

    method! class_property _loc (prop : ('loc, 'loc) Ast.Class.Property.t') =
      let open Ast.Class.Property in
      let { key; value; annot; static = _; variance; comments = _ } = prop in
      ignore @@ this#object_key key;
      ignore @@ this#type_annotation_hint annot;
      let hint =
        match annot with
        | Ast.Type.Available annot -> Hint_t (AnnotationHint (ALocMap.empty, annot))
        | Ast.Type.Missing _ -> Hint_None
      in
      this#visit_class_property_value ~hint value;
      ignore @@ this#variance_opt variance;
      prop

    method! class_private_field _loc (prop : ('loc, 'loc) Ast.Class.PrivateField.t') =
      let open Ast.Class.PrivateField in
      let { key; value; annot; static = _; variance; comments = _ } = prop in
      ignore @@ this#private_name key;
      ignore @@ this#type_annotation_hint annot;
      let hint =
        match annot with
        | Ast.Type.Available annot -> Hint_t (AnnotationHint (ALocMap.empty, annot))
        | Ast.Type.Missing _ -> Hint_None
      in
      this#visit_class_property_value ~hint value;
      ignore @@ this#variance_opt variance;
      prop

    method private visit_class_property_value ~hint value =
      let open Ast.Class.Property in
      match value with
      | Declared -> ()
      | Uninitialized -> ()
      | Initialized x -> this#visit_expression ~cond:NonConditionalContext ~hint x

    method! class_method _loc (meth : ('loc, 'loc) Ast.Class.Method.t') =
      let open Ast.Class.Method in
      let { kind = _; key; value = (_, value); static = _; decorators; comments = _ } = meth in
      let _ = this#object_key key in
      let scope_kind = func_scope_kind ~key value in
      let () =
        this#in_new_tparams_env ~keep:true (fun () ->
            this#visit_function ~scope_kind ~func_hint:Hint_None value
        )
      in
      let (_ : _ list) = map_list this#class_decorator decorators in
      meth

    method! declare_function loc (decl : ('loc, 'loc) Ast.Statement.DeclareFunction.t) =
      match Declare_function_utils.declare_function_to_function_declaration_simple loc decl with
      | Some stmt ->
        let _ = this#statement (loc, stmt) in
        decl
      | None ->
        let open Ast.Statement.DeclareFunction in
        let { id = (id_loc, _); annot; predicate = _; comments = _ } = decl in
        this#add_ordinary_binding
          id_loc
          (func_reason ~async:false ~generator:false loc)
          (Binding
             (Root
                (Annotation
                   {
                     tparams_map = ALocMap.empty;
                     optional = false;
                     default_expression = None;
                     is_assignment = false;
                     annot;
                   }
                )
             )
          );
        super#declare_function loc decl

    method! declare_class loc (decl : ('loc, 'loc) Ast.Statement.DeclareClass.t) =
      let open Ast.Statement.DeclareClass in
      let { id = (id_loc, { Ast.Identifier.name; _ }); _ } = decl in
      this#add_ordinary_binding
        id_loc
        (mk_reason (RClass (RIdentifier (OrdinaryName name))) loc)
        (DeclaredClass (loc, decl));
      super#declare_class loc decl

    method! assignment loc (expr : ('loc, 'loc) Ast.Expression.Assignment.t) =
      let open Ast.Expression.Assignment in
      let { operator; left = (lhs_loc, lhs_node) as left; right; comments = _ } = expr in
      let () =
        match (operator, lhs_node) with
        | (None, Ast.Pattern.Expression (member_loc, Ast.Expression.Member member)) ->
          (* Use super member to visit sub-expressions to avoid record a read of the member. *)
          ignore @@ super#member member_loc member;
          this#add_ordinary_binding
            member_loc
            (mk_pattern_reason left)
            (MemberAssign { member_loc; member; rhs = right })
        | (None, _) -> Destructure.pattern ~f:this#add_ordinary_binding (Root (Value right)) left
        | ( Some operator,
            Ast.Pattern.Identifier
              { Ast.Pattern.Identifier.name = (id_loc, { Ast.Identifier.name; _ }); _ }
          ) ->
          this#add_ordinary_binding
            id_loc
            (mk_reason (RIdentifier (OrdinaryName name)) id_loc)
            (OpAssign { exp_loc = loc; lhs = left; op = operator; rhs = right })
        | (Some operator, Ast.Pattern.Expression ((def_loc, _) as e)) ->
          (* In op_assign, the LHS will also be read. *)
          let cond =
            match operator with
            | AndAssign
            | OrAssign ->
              OtherConditionalTest
            | _ -> NonConditionalContext
          in
          this#visit_expression ~cond ~hint:Hint_None e;
          this#add_ordinary_binding
            def_loc
            (mk_pattern_reason left)
            (OpAssign { exp_loc = loc; lhs = left; op = operator; rhs = right })
        | _ -> ()
      in
      let is_provider =
        Flow_ast_utils.fold_bindings_of_pattern
          (fun acc (loc, _) -> acc || Env_api.Provider_api.is_provider providers loc)
          false
          left
      in
      let hint =
        if is_provider then
          Hint_None
        else
          match lhs_node with
          | Ast.Pattern.Identifier _ ->
            Env_api.Provider_api.providers_of_def providers lhs_loc
            |> Base.Option.value_map ~f:(fun x -> x.Env_api.Provider_api.providers) ~default:[]
            |> Base.List.map ~f:(fun { Env_api.Provider_api.reason; _ } ->
                   Reason.aloc_of_reason reason
               )
            |> Nel.of_list
            |> Base.Option.value_map ~default:Hint_None ~f:(fun providers ->
                   Hint_t (ProvidersHint providers)
               )
          | Ast.Pattern.Expression
              (_, Ast.Expression.Member { Ast.Expression.Member._object; property; comments = _ })
            ->
            (match property with
            | Ast.Expression.Member.PropertyIdentifier (_, { Ast.Identifier.name; comments = _ }) ->
              decompose_hint (Decomp_ObjProp name) (Hint_t (ValueHint _object))
            | Ast.Expression.Member.PropertyPrivateName _ ->
              (* TODO create a hint based on the current class. *)
              Hint_None
            | Ast.Expression.Member.PropertyExpression _ ->
              decompose_hint Decomp_ObjComputed (Hint_t (ValueHint _object)))
          | _ ->
            (* TODO create a hint based on the lhs pattern *)
            Hint_Placeholder
      in
      this#visit_expression ~hint ~cond:NonConditionalContext right;
      expr

    method! update_expression loc (expr : (ALoc.t, ALoc.t) Ast.Expression.Update.t) =
      let open Ast.Expression.Update in
      let { argument; operator; prefix = _; comments = _ } = expr in
      begin
        match argument with
        | (_, Ast.Expression.Identifier (id_loc, { Ast.Identifier.name; _ })) ->
{'fraction_non_alphanumeric': 0.09920792370203824, 'fraction_numerical': 5.878117239048333e-05, 'mean_word_length': 2.0803005612891545, 'pattern_counts': {'":': 0, '<': 33, '<?xml version=': 0, '>': 367, 'https://': 0, 'lorem ipsum': 0, 'www.': 0, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 0, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '4211773', 'n_tokens_mistral': 20818, 'n_tokens_neox': 20312, 'n_words': 5253}