(*
 * 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.
 *)

open Enclosing_context
open Name_def
open Type
open Reason
open FlowSymbol
open Loc_collections
open Utils_js
open Type_operation_utils
module Ast = Flow_ast
module EnvMap = Env_api.EnvMap
module Statement = Fix_statement.Statement_
module Anno = Type_annotation.Make (Statement)

let mk_tparams_map cx tparams_map =
  let { Loc_env.tparams; _ } = Context.environment cx in
  ALocMap.fold
    (fun l _ subst_map ->
      let (name, _, ty) = ALocMap.find l tparams in
      Subst_name.Map.add name ty subst_map)
    tparams_map
    Subst_name.Map.empty

let try_cache ~target_loc ~check ~cache cx =
  if Context.typing_mode cx <> Context.CheckingMode then begin
    let original_errors = Context.errors cx in
    Context.reset_errors cx Flow_error.ErrorSet.empty;
    let (produced_uncacheable_result, e) = Context.run_in_synthesis_mode ~target_loc cx check in
    let can_cache =
      (not produced_uncacheable_result) && Flow_error.ErrorSet.is_empty (Context.errors cx)
    in
    Context.reset_errors cx original_errors;
    if can_cache then cache e;
    e
  end else
    let e = check () in
    cache e;
    e

(* Helper to incrementally build PatternUnion state from a previous pattern location.
   Returns the pattern_union_state to use for the current pattern. *)
let get_pattern_union_state_from_prev node_cache cx prev_pattern_loc =
  match prev_pattern_loc with
  | None ->
    (* First pattern: start with empty PatternUnion *)
    (Match_pattern_ir.PatternUnion.empty, 0)
  | Some prev_loc ->
    (match Node_cache.get_match_pattern_union node_cache prev_loc with
    | Some prev_pattern_union_state ->
      let prev_pattern = Base.Option.value_exn (Node_cache.get_match_pattern node_cache prev_loc) in
      Exhaustive.PatternUnionBuilder.add_pattern
        cx
        ~raise_errors:false
        prev_pattern_union_state
        prev_pattern
        ~last:false
    | None -> failwith "ERROR: pattern_union cache missing - dependency ordering issue")

let expression cx ?encl_ctx ?decl ?as_const exp =
  let cache = Context.node_cache cx in
  let target_loc =
    match Context.typing_mode cx with
    | Context.SynthesisMode { target_loc } -> target_loc
    | _ -> None
  in
  let ((_, t), _) =
    try_cache
      cx
      ~target_loc
      ~check:(fun () -> Statement.expression ?encl_ctx ?decl ?as_const cx exp)
      ~cache:(Node_cache.set_expression cache)
  in
  t

let make_hooklike cx t =
  if Context.hook_compatibility cx then
    match t with
    | DefT (_, TypeT _) -> t
    | _ ->
      let reason = TypeUtil.reason_of_t t in
      Flow_js_utils.map_on_resolved_type cx reason t (fun t ->
          Tvar_resolver.mk_tvar_and_fully_resolve_no_wrap_where cx reason (fun tvar ->
              Flow_js.flow cx (t, HooklikeT tvar)
          )
      )
  else
    t

let resolve_annotation cx tparams_map ?(react_deep_read_only = None) anno =
  let cache = Context.node_cache cx in
  let tparams_map = mk_tparams_map cx tparams_map in
  let (t, anno) = Anno.mk_type_available_annotation cx tparams_map anno in
  let t =
    match react_deep_read_only with
    | Some ((_, kind) as param_loc) ->
      let enabled =
        match kind with
        | HookArg
        | Props ->
          Context.react_rule_enabled cx Options.DeepReadOnlyProps
        | HookReturn -> Context.react_rule_enabled cx Options.DeepReadOnlyHookReturns
        | DebugAnnot -> true
      in
      if enabled then
        Flow_js.mk_possibly_evaluated_destructor_for_annotations
          cx
          unknown_use
          (TypeUtil.reason_of_t t)
          t
          (ReactDRO param_loc)
          (Eval.generate_id ())
      else
        t
    | _ -> t
  in
  if Context.typing_mode cx = Context.CheckingMode then Node_cache.set_annotation cache anno;
  t

let rec synthesizable_expression cx ?(encl_ctx = NoContext) exp =
  let open Ast.Expression in
  match exp with
  | (loc, Identifier (_, name)) -> Statement.identifier cx ~encl_ctx name loc
  | (loc, StringLiteral lit) -> Statement.string_literal cx ~encl_ctx loc lit
  | (loc, BooleanLiteral lit) -> Statement.boolean_literal cx ~encl_ctx loc lit
  | (loc, NullLiteral _) -> Statement.null_literal loc
  | (loc, NumberLiteral lit) -> Statement.number_literal cx ~encl_ctx loc lit
  | (loc, BigIntLiteral lit) -> Statement.bigint_literal cx ~encl_ctx loc lit
  | (loc, RegExpLiteral _) -> Statement.regexp_literal cx loc
  | (loc, ModuleRefLiteral lit) ->
    let (t, _lit) = Statement.module_ref_literal cx loc lit in
    t
  | (_, AsExpression { AsExpression.annot; _ })
  | (_, TypeCast { TypeCast.annot; _ }) ->
    resolve_annotation cx ALocMap.empty annot
  | ( loc,
      Ast.Expression.Member
        {
          Ast.Expression.Member._object;
          property =
            Ast.Expression.Member.PropertyIdentifier (ploc, { Ast.Identifier.name; comments = _ });
          comments = _;
        }
    ) ->
    let t = synthesizable_expression cx ~encl_ctx _object in
    let tout =
      match Refinement.get ~allow_optional:false cx exp loc with
      | Some t -> t
      | None ->
        let expr_reason = mk_expression_reason exp in
        let prop_reason = mk_reason (RProperty (Some (OrdinaryName name))) ploc in
        let use_op = Op (GetProperty expr_reason) in
        Statement.get_prop
          ~use_op (* TODO(jmbrown) This feels incorrect *)
          ~hint:(Type_env.get_hint cx loc)
          ~encl_ctx:NoContext
          cx
          expr_reason
          t
          (prop_reason, name)
    in
    tout
  | _ -> expression cx ~encl_ctx exp

let mk_selector_reason_has_default cx loc = function
  | Selector.Elem { index = n; has_default } ->
    let key =
      DefT
        ( mk_reason RNumber loc,
          SingletonNumT { value = (float n, string_of_int n); from_annot = false }
        )
    in
    (Type.Elem key, mk_reason (RArrayNthElement n) loc, has_default)
  | Selector.Prop { prop; prop_loc; has_default } ->
    ( Type.Prop (prop, has_default),
      mk_reason (RProperty (Some (OrdinaryName prop))) prop_loc,
      has_default
    )
  | Selector.ArrRest n -> (Type.ArrRest n, mk_reason RArrayPatternRestProp loc, false)
  | Selector.ObjRest { used_props; after_computed = _ } ->
    (* TODO: eveyrthing after a computed prop should be optional *)
    (Type.ObjRest used_props, mk_reason RObjectPatternRestProp loc, false)
  | Selector.Computed { expression = exp; has_default } ->
    let t = expression cx ~encl_ctx:IndexContext exp in
    (Type.Elem t, mk_reason (RProperty None) loc, has_default)
  | Selector.Default -> (Type.Default, mk_reason RDefaultValue loc, false)

let synthesize_expression_for_instantiation ~target_loc cx e =
  let cache = Context.node_cache cx in
  try_cache
    cx
    ~target_loc
    ~check:(fun () -> Statement.expression cx e)
    ~cache:(Node_cache.set_expression cache)

let synthesize_jsx_children_for_instantiation cx children =
  let cache = Context.node_cache cx in
  try_cache
    cx
    ~target_loc:None
    ~check:(fun () -> Statement.collapse_children cx children)
    ~cache:(Node_cache.set_jsx_children cache)

let synth_arg_list ~target_loc cx (_loc, { Ast.Expression.ArgList.arguments; comments = _ }) =
  Base.List.map
    arguments
    ~f:
      (let open Ast.Expression in
      function
      | Expression e ->
        let ((loc, t), _) = synthesize_expression_for_instantiation ~target_loc cx e in
        (loc, Arg t)
      | Spread (_, { SpreadElement.argument = e; comments = _ }) ->
        let ((loc, t), _) = synthesize_expression_for_instantiation ~target_loc cx e in
        (loc, SpreadArg t)
      )

let resolve_hint cx loc hint : Type_hint.concr_hint =
  let rec resolve_hint_node = function
    | AnnotationHint (tparams_locs, anno) -> resolve_annotation cx tparams_locs anno
    | ValueHint (encl_ctx, exp) -> expression cx ~encl_ctx exp
    | ProvidersHint (loc, []) -> Type_env.checked_find_loc_env_write cx Env_api.OrdinaryNameLoc loc
    | ProvidersHint (l1, l2 :: rest) ->
      let t1 = Type_env.checked_find_loc_env_write cx Env_api.OrdinaryNameLoc l1 in
      let t2 = Type_env.checked_find_loc_env_write cx Env_api.OrdinaryNameLoc l2 in
      let ts =
        Base.List.map rest ~f:(fun loc ->
            Type_env.checked_find_loc_env_write cx Env_api.OrdinaryNameLoc loc
        )
      in
      UnionT (mk_reason RProviders loc, UnionRep.make ~kind:UnionRep.ProvidersKind t1 t2 ts)
    | WriteLocHint (kind, loc) -> Type_env.checked_find_loc_env_write cx kind loc
    | StringLiteralType name ->
      DefT
        ( mk_reason (RIdentifier (OrdinaryName name)) loc,
          SingletonStrT { from_annot = true; value = OrdinaryName name }
        )
    | ReactFragmentType ->
      Flow_js_utils.ImportExportUtils.get_implicitly_imported_react_type
        cx
        loc
        ~singleton_concretize_type_for_imports_exports:
          Flow_js.singleton_concretize_type_for_imports_exports
        ~purpose:Flow_intermediate_error_types.ReactModuleForJSXFragment
    | ReactNodeType ->
      let reason = mk_reason (RType (OrdinaryName "React.Node")) loc in
      Flow_js.get_builtin_react_type
        cx
        reason
        Flow_intermediate_error_types.ReactModuleForReactNodeType
    | AnyErrorHint reason -> AnyT.error reason
    | ComposedArrayPatternHint (loc, elements) ->
      let reason = mk_reason RDestructuring loc in
      let elem_spread_list =
        Base.List.map elements ~f:(function
            | ArrayElementPatternHint h ->
              let t = resolve_hint_node h in
              let reason = mk_reason RArrayElement loc in
              UnresolvedArg (TypeUtil.mk_tuple_element reason t, None)
            | ArrayRestElementPatternHint h -> UnresolvedSpreadArg (resolve_hint_node h)
            )
      in
      Tvar.mk_where cx reason (fun tout ->
          let reason_op = reason in
          let element_reason =
            replace_desc_reason
              (Reason.RInferredUnionElemArray
                 { instantiable = false; is_empty = List.is_empty elements }
              )
              reason_op
          in
          let elem_t = Tvar.mk cx element_reason in
          Flow_js.resolve_spread_list
            cx
            ~use_op:unknown_use
            ~reason_op
            elem_spread_list
            (ResolveSpreadsToArrayLiteral { id = Reason.mk_id (); as_const = false; elem_t; tout })
      )
    | ComposedObjectPatternHint (loc, properties) ->
      let acc =
        Base.List.fold properties ~init:(Statement.ObjectExpressionAcc.empty ()) ~f:(fun acc -> function
          | ObjectPropPatternHint (n, l, h) ->
            Statement.ObjectExpressionAcc.add_prop
              (Properties.add_field
                 (OrdinaryName n)
                 Polarity.Neutral
                 ~key_loc:(Some l)
                 (resolve_hint_node h)
              )
              acc
          | ObjectSpreadPropPatternHint h ->
            Statement.ObjectExpressionAcc.add_spread (resolve_hint_node h) acc
        )
      in
      let reason = mk_reason RDestructuring loc in
      Statement.ObjectExpressionAcc.mk_object_from_spread_acc
        cx
        acc
        reason
        ~as_const:false
        ~frozen:false
        ~default_proto:(ObjProtoT reason)
  in
  let map_base_hint = resolve_hint_node in
  let map_targs = Statement.convert_call_targs_opt' cx in
  let map_arg_list arg_list ~target_loc =
    let cache = Context.hint_map_arglist_cache cx in
    let (l, _) = arg_list in
    match Hashtbl.find_opt cache (l, target_loc) with
    | Some result -> result
    | None ->
      let result = synth_arg_list ~target_loc cx arg_list in
      Hashtbl.add cache (l, target_loc) result;
      result
  in
  let map_jsx reason name (props, children) =
    let cache = Context.hint_map_jsx_cache cx in
    let key =
      ( reason,
        name,
        Base.List.map props ~f:(function
            | Ast.JSX.Opening.Attribute (l, _)
            | Ast.JSX.Opening.SpreadAttribute (l, _)
            -> l
            ),
        fst children
      )
    in
    match Hashtbl.find_opt cache key with
    | Some result -> result
    | None ->
      let original_errors = Context.errors cx in
      Context.reset_errors cx Flow_error.ErrorSet.empty;
      let props =
        lazy
          (let (props, _, _, _) =
             Statement.jsx_mk_props
               cx
               reason
               ~check_expression:(fun ?has_hint:_ ->
                 synthesize_expression_for_instantiation ~target_loc:None)
               ~collapse_children:synthesize_jsx_children_for_instantiation
               name
               props
               children
           in
           props
          )
      in
      Context.reset_errors cx original_errors;
      Hashtbl.add cache key props;
      props
  in
  Hint.map hint ~map_base_hint ~map_targs ~map_arg_list ~map_jsx

let resolve_hints cx loc = Base.List.map ~f:(resolve_hint cx loc)

let lazily_resolve_hints cx loc hints =
  let has_hint = not @@ Base.List.is_empty hints in
  let lazy_hint ~expected_only ?skip_optional reason =
    resolve_hints cx loc hints |> Type_hint.evaluate_hints cx ~expected_only ?skip_optional reason
  in
  (has_hint, lazy_hint)

let resolve_pred_func cx (class_stack, ex, callee, targs, arguments) =
  lazy
    (let (loc, _) = callee in
     let use_op =
       Op
         (FunCall
            {
              op = Reason.mk_expression_reason ex;
              fn = Reason.mk_expression_reason callee;
              args = Reason.mk_initial_arguments_reason arguments;
              local = true;
            }
         )
     in
     (* [callee] might be a member access expression. Since we are explicitly unbinding it from
      * the call, make sure we don't raise a method-unbinding error. *)
     let callee =
       Type_env.with_class_stack cx class_stack ~f:(fun () ->
           (* Synthesis mode to avoid caching *)
           let (_, (t, new_errors)) =
             Context.run_in_synthesis_mode_with_errors cx ~target_loc:None ~f:(fun () ->
                 expression cx callee
             )
           in
           if Flow_error.is_lint_only_errorset new_errors then
             t
           else
             AnyT.at (AnyError None) loc
       )
     in
     let targs = Statement.convert_call_targs_opt' cx targs in
     let (_, { Ast.Expression.ArgList.arguments; comments = _ }) = arguments in
     let argts = Base.List.map arguments ~f:(fun e -> fst (Statement.expression_or_spread cx e)) in
     (use_op, fst ex, callee, targs, argts)
    )

let resolve_annotated_function
    cx ~scope_kind ~bind_this ~statics ~hook_like reason tparams_map function_loc function_ =
  let { Ast.Function.sig_loc; effect_; async; _ } = function_ in
  if
    (scope_kind = ComponentOrHookBody || scope_kind = AsyncComponentOrHookBody)
    && effect_ = Ast.Function.Hook
  then begin
    Flow_js_utils.add_output cx Error_message.(ENestedHook reason)
  end;
  if async && effect_ = Ast.Function.Hook && not (Context.async_component_syntax cx) then
    Flow_js_utils.add_output
      cx
      (Error_message.EUnsupportedSyntax (function_loc, Flow_intermediate_error_types.AsyncHookSyntax)
      );
  let cache = Context.node_cache cx in
  let tparams_map = mk_tparams_map cx tparams_map in
  let default_this = Flow_js_utils.default_this_type cx ~needs_this_param:bind_this function_ in
  let ((func_sig, _) as sig_data) =
    Statement.mk_func_sig
      cx
      ~require_return_annot:false
      ~constructor:false
      ~getset:false
      ~statics
      tparams_map
      reason
      function_
  in
  Node_cache.set_function_sig cache sig_loc sig_data;
  let t =
    Statement.Func_stmt_sig.functiontype
      cx
      ~arrow:(not bind_this)
      (Some function_loc)
      default_this
      func_sig
  in
  if effect_ <> Ast.Function.Hook && hook_like then
    make_hooklike cx t
  else
    t

let resolve_annotated_component cx scope_kind reason tparams_map component_loc component =
  if not (Context.component_syntax cx) then begin
    Flow_js_utils.add_output
      cx
      (Error_message.EUnsupportedSyntax
         (component_loc, Flow_intermediate_error_types.ComponentSyntax)
      );
    AnyT.at (AnyError None) component_loc
  end else begin
    if scope_kind = ComponentOrHookBody || scope_kind = AsyncComponentOrHookBody then begin
      Flow_js_utils.add_output cx Error_message.(ENestedComponent reason)
    end;
    if component.Ast.Statement.ComponentDeclaration.async && not (Context.async_component_syntax cx)
    then
      Flow_js_utils.add_output
        cx
        (Error_message.EUnsupportedSyntax
           (component_loc, Flow_intermediate_error_types.AsyncComponentSyntax)
        );
    let tparams_map = mk_tparams_map cx tparams_map in
    let { Ast.Statement.ComponentDeclaration.sig_loc; body; _ } = component in
    let ((component_sig, _) as sig_data) =
      Statement.mk_component_sig cx tparams_map reason component
    in
    let cache = Context.node_cache cx in
    Node_cache.set_component_sig cache sig_loc sig_data;
    let in_annotation = Option.is_none body in
    Statement.Component_declaration_sig.component_type cx ~in_annotation component_sig
  end

let rec binding_has_annot = function
  | Root (Annotation _) -> true
  | Hooklike b
  | Select { parent = (_, b); _ } ->
    binding_has_annot b
  | _ -> false

let rec resolve_binding cx def_scope_kind reason loc b =
  match b with
  | Root
      (Annotation
        {
          tparams_map;
          optional;
          has_default_expression;
          param_loc;
          annot;
          react_deep_read_only;
          concrete = _;
        }
        ) ->
    let t =
      resolve_annotation
        cx
        tparams_map
        ~react_deep_read_only:
          (match (param_loc, react_deep_read_only) with
          | (Some param_loc, Some Comp) -> Some (param_loc, Props)
          | (Some param_loc, Some Hook) -> Some (param_loc, HookArg)
          | _ -> None)
        annot
    in
    Base.Option.iter param_loc ~f:(Type_env.bind_function_param cx t);
    if optional && not has_default_expression then
      TypeUtil.optional t
    else
      t
  | Root (Value { hints = _; expr; decl_kind; as_const }) ->
    expression cx ?decl:decl_kind ~as_const expr
  | Root (MatchCaseRoot { case_match_root_loc; root_pattern_loc; prev_pattern_loc }) ->
    let unfiltered_t =
      Type_env.var_ref cx (OrdinaryName Flow_ast_utils.match_root_name) case_match_root_loc
    in
    let node_cache = Context.node_cache cx in
    let pattern_union_state = get_pattern_union_state_from_prev node_cache cx prev_pattern_loc in
    (* Optimization: if the previous value_left was only inexhaustible (e.g., just `string`),
       filtering won't change it, so we can reuse it directly. *)
    let value_left =
      Base.Option.bind prev_pattern_loc ~f:(fun prev_loc ->
          Base.Option.filter
            (Node_cache.get_match_pattern_value_union node_cache prev_loc)
            ~f:Match_pattern_ir.ValueUnion.is_only_inexhaustible
      )
    in
    let value_left =
      match value_left with
      | Some value_left -> value_left
      | None ->
        let pattern_union = Exhaustive.PatternUnionBuilder.finalize (fst pattern_union_state) in
        Exhaustive.filter_by_pattern_union cx unfiltered_t pattern_union
    in
    Node_cache.set_match_pattern_value_union node_cache root_pattern_loc value_left;
    Node_cache.set_match_pattern_union node_cache root_pattern_loc pattern_union_state;
    Match_pattern_ir.ValueUnion.to_type (TypeUtil.reason_of_t unfiltered_t) value_left
  | Root (ObjectValue { obj_loc = loc; obj; synthesizable = ObjectSynthesizable _ }) ->
    let open Ast.Expression.Object in
    let resolve_prop ~bind_this ~prop_loc ~fn_loc fn =
      let reason = func_reason ~async:false ~generator:false prop_loc in
      resolve_annotated_function
        cx
        ~scope_kind:def_scope_kind
        ~bind_this
        ~hook_like:false
        ~statics:SMap.empty
        reason
        ALocMap.empty
        fn_loc
        fn
    in

    let rec mk_obj obj_loc { properties; _ } =
      let rec mk_expression (loc, expr) =
        match expr with
        | Ast.Expression.StringLiteral _
        | Ast.Expression.NumberLiteral _
        | Ast.Expression.NullLiteral _
        | Ast.Expression.BooleanLiteral _
        | Ast.Expression.BigIntLiteral _
        | Ast.Expression.RegExpLiteral _
        | Ast.Expression.ModuleRefLiteral _
        | Ast.Expression.Identifier _
        | Ast.Expression.TypeCast _
        | Ast.Expression.AsConstExpression _
        | Ast.Expression.AsExpression _
        | Ast.Expression.Member _ ->
          synthesizable_expression cx (loc, expr)
        | Ast.Expression.Function fn
        | Ast.Expression.ArrowFunction fn ->
          let { Ast.Function.sig_loc; _ } = fn in
          let bind_this =
            match expr with
            | Ast.Expression.Function _ -> true
            | _ -> false
          in
          resolve_prop ~bind_this ~prop_loc:sig_loc ~fn_loc:loc fn
        | Ast.Expression.Object obj -> mk_obj loc obj
        | Ast.Expression.Array { Ast.Expression.Array.elements = []; _ } ->
          let (_, elem_t) = Statement.empty_array cx loc in
          DefT
            (reason, ArrT (ArrayAT { elem_t; tuple_view = Some empty_tuple_view; react_dro = None }))
        | Ast.Expression.Array { Ast.Expression.Array.elements; _ } ->
          (* TODO merge code with statement.ml implementation *)
          let array_elements cx undef_loc =
            let open Ast.Expression.Array in
            Base.List.map ~f:(fun e ->
                match e with
                | Expression e ->
                  let t = mk_expression e in
                  let (loc, _) = e in
                  let reason = mk_reason RArrayElement loc in
                  UnresolvedArg (TypeUtil.mk_tuple_element reason t, None)
                | Hole hole_loc ->
                  let t = EmptyT.at undef_loc in
                  let reason = mk_reason RArrayElement hole_loc in
                  UnresolvedArg (TypeUtil.mk_tuple_element reason t, None)
                | Spread (_, { Ast.Expression.SpreadElement.argument; comments = _ }) ->
                  let t = synthesizable_expression cx argument in
                  UnresolvedSpreadArg t
            )
          in
          let reason = mk_reason RArrayLit loc in
          let elem_spread_list = array_elements cx loc elements in
          Tvar.mk_where cx reason (fun tout ->
              let reason_op = reason in
              let element_reason =
                replace_desc_reason
                  (Reason.RInferredUnionElemArray
                     { instantiable = false; is_empty = List.is_empty elements }
                  )
                  reason_op
              in
              let elem_t = Tvar.mk cx element_reason in
              let resolve_to =
                ResolveSpreadsToArrayLiteral { id = mk_id (); as_const = false; elem_t; tout }
              in
              Flow_js.resolve_spread_list
                cx
                ~use_op:unknown_use
                ~reason_op
                elem_spread_list
                resolve_to
          )
        | Ast.Expression.Assignment _
        | Ast.Expression.Binary _
        | Ast.Expression.Call _
        | Ast.Expression.Class _
        | Ast.Expression.Conditional _
        | Ast.Expression.Import _
        | Ast.Expression.JSXElement _
        | Ast.Expression.JSXFragment _
        | Ast.Expression.Logical _
        | Ast.Expression.Match _
        | Ast.Expression.MetaProperty _
        | Ast.Expression.New _
        | Ast.Expression.OptionalCall _
        | Ast.Expression.OptionalMember _
        | Ast.Expression.Record _
        | Ast.Expression.Sequence _
        | Ast.Expression.Super _
        | Ast.Expression.TaggedTemplate _
        | Ast.Expression.TemplateLiteral _
        | Ast.Expression.This _
        | Ast.Expression.TSSatisfies _
        | Ast.Expression.Unary _
        | Ast.Expression.Update _
        | Ast.Expression.Yield _ ->
          failwith "Object not synthesizable"
      in
      let reason = mk_reason RObjectLit obj_loc in
      let obj_proto = ObjProtoT reason in
      let acc =
        Base.List.fold properties ~init:(Statement.ObjectExpressionAcc.empty ()) ~f:(fun acc prop ->
            match prop with
            | SpreadProperty
                (_, { SpreadProperty.argument = (_, Ast.Expression.Identifier _) as exp; _ }) ->
              let spread = synthesizable_expression cx exp in
              Statement.ObjectExpressionAcc.add_spread spread acc
            | Property
                ( prop_loc,
                  Property.Method
                    {
                      key =
                        ( Property.Identifier (name_loc, { Ast.Identifier.name; comments = _ })
                        | Property.StringLiteral (name_loc, { Ast.StringLiteral.value = name; _ }) );
                      value = (fn_loc, fn);
                    }
                ) ->
              let t = resolve_prop ~bind_this:false ~prop_loc ~fn_loc fn in
              Statement.ObjectExpressionAcc.add_prop
                (Properties.add_method (OrdinaryName name) (Some name_loc) t)
                acc
            | Property
                ( _,
                  Property.Init
                    {
                      key =
                        ( Property.Identifier (name_loc, { Ast.Identifier.name; comments = _ })
                        | Property.StringLiteral (name_loc, { Ast.StringLiteral.value = name; _ }) );
                      value;
                      _;
                    }
                ) ->
              let t = mk_expression value in
              Statement.ObjectExpressionAcc.add_prop
                (Properties.add_field
                   (OrdinaryName name)
                   Polarity.Neutral
                   ~key_loc:(Some name_loc)
                   t
                )
                acc
            | _ -> failwith "Object not synthesizable"
        )
      in
      Statement.ObjectExpressionAcc.mk_object_from_spread_acc
        cx
        acc
        reason
        ~as_const:false
        ~frozen:false
        ~default_proto:obj_proto
    in
    mk_obj loc obj
  | Root (ObjectValue { obj_loc; obj; _ }) ->
    let expr = (obj_loc, Ast.Expression.Object obj) in
    expression cx expr
  | Root
      (FunctionValue
        {
          hints = _;
          synthesizable_from_annotation = FunctionSynthesizable;
          function_loc;
          function_;
          statics;
          arrow;
          tparams_map;
        }
        ) ->
    let cache = Context.node_cache cx in
    let tparams_map = mk_tparams_map cx tparams_map in
    let { Ast.Function.sig_loc; async; generator; _ } = function_ in
    let reason_fun =
      func_reason
        ~async
        ~generator
        ( if arrow then
          function_loc
        else
          sig_loc
        )
    in
    let default_this = Flow_js_utils.default_this_type cx ~needs_this_param:(not arrow) function_ in
    let ((func_sig, _) as sig_data) =
      Statement.mk_func_sig
        cx
        ~require_return_annot:false
        ~constructor:false
        ~getset:false
        ~statics
        tparams_map
        reason_fun
        function_
    in
    let t =
      Statement.Func_stmt_sig.functiontype cx ~arrow (Some function_loc) default_this func_sig
    in
    Node_cache.set_function_sig cache sig_loc sig_data;
    t
  | Root
      (FunctionValue
        {
          hints = _;
          synthesizable_from_annotation = _;
          function_loc;
          function_;
          statics;
          arrow;
          tparams_map = _;
        }
        ) ->
    let { Ast.Function.id; async; generator; sig_loc; _ } = function_ in
    let reason_fun =
      func_reason
        ~async
        ~generator
        ( if arrow then
          function_loc
        else
          sig_loc
        )
    in
    let func =
      if arrow then
        Statement.mk_arrow cx ~statics reason_fun function_
      else
        Statement.mk_function cx ~needs_this_param:true ~statics reason_fun function_loc function_
    in
    let (func_type, func_ast) = func in
    let cache = Context.node_cache cx in
    (match id with
    | Some (id_loc, _) -> Node_cache.set_function cache id_loc func
    | None -> Node_cache.set_function cache function_loc func);
    let expr =
      ( (function_loc, func_type),
        if arrow then
          Ast.Expression.ArrowFunction func_ast
        else
          Ast.Expression.Function func_ast
      )
    in
    Node_cache.set_expression cache expr;
    func_type
  | Root (EmptyArray { array_providers; arr_loc }) ->
    let (elem_t, tuple_view, reason) =
      if ALocSet.cardinal array_providers > 0 then
        let ts =
          ALocSet.elements array_providers
          |> Base.List.map ~f:(Type_env.checked_find_loc_env_write cx Env_api.ArrayProviderLoc)
        in
        let elem_t =
          Tvar.mk_where cx (mk_reason REmptyArrayElement loc) (fun tvar ->
              Base.List.iter ~f:(fun t -> Flow_js.flow cx (t, UseT (unknown_use, tvar))) ts
          )
        in
        (elem_t, None, reason)
      else
        let elem_t = EmptyT.make (mk_reason REmptyArrayElement loc) in
        Flow_js_utils.add_output cx Error_message.(EEmptyArrayNoProvider { loc });
        (elem_t, Some empty_tuple_view, replace_desc_reason REmptyArrayLit reason)
    in
    let t = DefT (reason, ArrT (ArrayAT { elem_t; tuple_view; react_dro = None })) in
    let cache = Context.node_cache cx in
    let exp =
      ((arr_loc, t), Flow_ast.Expression.(Array { Array.elements = []; comments = None }))
    in
    Node_cache.set_expression cache exp;
    t
  | Root (Contextual { reason; hints; optional; default_expression }) ->
    let param_loc = Reason.loc_of_reason reason in
    let t =
      let (has_hint, lazy_hint) = lazily_resolve_hints cx loc hints in
      match lazy_hint ~expected_only:false reason with
      | HintAvailable (t, _) ->
        let t =
          if Option.is_some default_expression then
            Tvar.mk_where cx reason (fun tout ->
                Flow_js.flow cx (t, FilterOptionalT (unknown_use, tout))
            )
          else
            t
        in
        TypeUtil.mod_reason_of_t (Base.Fn.const reason) t
      | NoHint
      | DecompositionError
      | EncounteredPlaceholder ->
        if has_hint then
          Flow_js_utils.add_output
            cx
            (Error_message.EMissingLocalAnnotation
               { reason; hint_available = true; from_generic_function = false }
            );
        AnyT (reason, AnyError (Some MissingAnnotation))
    in
    let () =
      match hints with
      | [] ->
        Flow_js_utils.add_output
          cx
          (Error_message.EMissingLocalAnnotation
             { reason; hint_available = false; from_generic_function = false }
          )
      | _ -> ()
    in
    Type_env.bind_function_param cx t param_loc;
    if optional && default_expression = None then
      TypeUtil.optional t
    else
      t
  | Root (UnannotatedParameter reason) ->
    let t = AnyT (reason, AnyError (Some MissingAnnotation)) in
    Type_env.bind_function_param cx t (loc_of_reason reason);
    Flow_js_utils.add_output
      cx
      (Error_message.EMissingLocalAnnotation
         { reason; hint_available = false; from_generic_function = false }
      );
    t
  | Root CatchUnannotated ->
    if Context.use_unknown_in_catch_variables cx then
      MixedT.at loc
    else
      AnyT (mk_reason RAnyImplicit loc, AnyError (Some MissingAnnotation))
  | Root DeclareVariableMissingAnnotationAndInit ->
    AnyT (mk_reason RAnyImplicit loc, AnyError (Some MissingAnnotation))
  | Root (For (kind, exp)) ->
    let right_t = expression cx ~encl_ctx:OtherTestContext exp in
    (match kind with
    | In ->
      TypeAssertions.assert_for_in_rhs cx right_t;
      StrModuleT.at loc
    | Of { await } ->
      let reason = mk_reason RForOfElement loc (*TODO: loc should be loc of loop *) in
      Statement.for_of_elemt cx right_t reason await)
  | Hooklike binding ->
    let t = resolve_binding cx def_scope_kind reason loc binding in
    make_hooklike cx t
  | Select { selector; parent = (parent_loc, binding) } ->
    let node_cache = Context.node_cache cx in
    let filtered_pattern_type =
      Node_cache.get_match_pattern_value_union node_cache parent_loc
      |> Base.Option.bind ~f:(Match_pattern_ir.ValueUnion.select ~selector)
      |> Base.Option.map ~f:(fun value_left ->
             Node_cache.set_match_pattern_value_union node_cache loc value_left;
             Match_pattern_ir.ValueUnion.to_type reason value_left
         )
    in
    (match filtered_pattern_type with
    | Some t -> t
    | None ->
      let refined_type =
        match selector with
        | Selector.Prop { prop; prop_loc; _ } ->
          let desc = RProperty (Some (OrdinaryName prop)) in
          Type_env.get_refinement cx desc prop_loc
        | _ -> None
      in
      (match refined_type with
      | Some t ->
        (* When we can get a refined value on a destructured property,
           we must be in an assignment position and the type must have been resolved. *)
        t
      | None ->
        let t = Type_env.checked_find_loc_env_write cx Env_api.PatternLoc parent_loc in
        let has_anno = binding_has_annot binding in
        let (selector, reason, has_default) = mk_selector_reason_has_default cx loc selector in
        let kind =
          if has_anno then
            DestructAnnot
          else
            DestructInfer
        in
        let t =
          Flow_js_utils.map_on_resolved_type cx reason t (fun t ->
              Tvar_resolver.mk_tvar_and_fully_resolve_no_wrap_where cx reason (fun tout ->
                  Flow_js.flow cx (t, DestructuringT (reason, kind, selector, tout, Reason.mk_id ()))
              )
          )
        in
        if has_default then
          let (selector, reason, _) = mk_selector_reason_has_default cx loc Selector.Default in
          Flow_js_utils.map_on_resolved_type cx reason t (fun t ->
              Tvar_resolver.mk_tvar_and_fully_resolve_no_wrap_where cx reason (fun tout ->
                  Flow_js.flow cx (t, DestructuringT (reason, kind, selector, tout, Reason.mk_id ()))
              )
          )
        else
          t))

let resolve_inferred_function
    cx ~scope_kind ~statics ~needs_this_param id_loc reason function_loc function_ =
  let cache = Context.node_cache cx in
  let ((fun_type, _) as fn) =
    Statement.mk_function cx ~needs_this_param ~statics reason function_loc function_
  in
  Node_cache.set_function cache id_loc fn;
  if
    (scope_kind = ComponentOrHookBody || scope_kind = AsyncComponentOrHookBody)
    && function_.Ast.Function.effect_ = Ast.Function.Hook
  then begin
    Flow_js_utils.add_output cx Error_message.(ENestedHook reason)
  end;
  if
    function_.Ast.Function.async
    && function_.Ast.Function.effect_ = Ast.Function.Hook
    && not (Context.async_component_syntax cx)
  then
    Flow_js_utils.add_output
      cx
      (Error_message.EUnsupportedSyntax (function_loc, Flow_intermediate_error_types.AsyncHookSyntax)
      );
  if
    function_.Ast.Function.effect_ <> Ast.Function.Hook
    && Base.Option.is_some (Flow_ast_utils.hook_function function_)
  then
    make_hooklike cx fun_type
  else
    fun_type

let resolve_class cx id_loc reason ~kind class_loc class_ =
  let cache = Context.node_cache cx in
  let inst_kind =
    match kind with
    | ClassKind.Class -> ClassKind
    | ClassKind.Record { defaulted_props } -> RecordKind { defaulted_props }
  in
  let ((class_t, class_t_internal, _, _) as sig_info) =
    Statement.mk_class_sig cx ~name_loc:id_loc ~class_loc ~inst_kind reason class_
  in
  Node_cache.set_class_sig cache class_loc sig_info;
  Type_env.bind_class_self_type cx class_t_internal class_loc;
  class_t

let resolve_record cx id_loc reason record_loc defaulted_props record =
  let cache = Context.node_cache cx in
  let ((record_t, record_t_internal, _, _) as sig_info) =
    Statement.mk_record_sig cx ~name_loc:id_loc ~record_loc ~defaulted_props reason record
  in
  Node_cache.set_record_sig cache record_loc sig_info;
  Type_env.bind_class_self_type cx record_t_internal record_loc;
  record_t

let resolve_op_assign cx ~exp_loc lhs assertion op rhs =
  let open Ast.Expression in
  let reason = mk_reason (RCustom (Flow_ast_utils.string_of_assignment_operator op)) exp_loc in
  match op with
  | Assignment.PlusAssign
  | Assignment.MinusAssign
  | Assignment.MultAssign
  | Assignment.ExpAssign
  | Assignment.DivAssign
  | Assignment.ModAssign
  | Assignment.LShiftAssign
  | Assignment.RShiftAssign
  | Assignment.RShift3Assign
  | Assignment.BitOrAssign
  | Assignment.BitXorAssign
  | Assignment.BitAndAssign ->
    (* lhs (op)= rhs *)
    let ((lhs_loc, lhs_t), _) = Statement.assignment_lhs cx lhs in
    let lhs_t =
      if assertion then
        let reason = mk_reason RNonnullAssert lhs_loc in
        Operators.non_maybe cx reason lhs_t
      else
        lhs_t
    in
    let rhs_t = expression cx rhs in
    Operators.arith cx reason (ArithKind.arith_kind_of_assignment_operator op) lhs_t rhs_t
  | Assignment.AndAssign
  | Assignment.OrAssign
  | Assignment.NullishAssign ->
    let ((_, lhs_t), _) = Statement.assignment_lhs cx lhs in
    let (((_, rhs_t), _), right_throws) =
      Abnormal.catch_expr_control_flow_exception (fun () -> Statement.expression cx rhs)
    in
    let rhs_t =
      if right_throws then
        EmptyT.at exp_loc
      else
        rhs_t
    in
    (match op with
    | Assignment.NullishAssign -> Operators.logical_nullish_coalesce cx reason lhs_t rhs_t
    | Assignment.AndAssign -> Operators.logical_and cx reason lhs_t rhs_t
    | Assignment.OrAssign -> Operators.logical_or cx reason lhs_t rhs_t
    | _ -> assert_false "Bad conditional guard")

let resolve_update cx ~id_loc ~exp_loc id_reason =
  let reason = mk_reason RUpdate exp_loc in
  let id_t = Type_env.ref_entry_exn ~lookup_mode:Type_env.LookupMode.ForValue cx id_loc id_reason in
  Operators.unary_arith cx reason UnaryArithKind.Update id_t

let resolve_type_alias cx loc alias =
  let cache = Context.node_cache cx in
  let (t, ast) = Statement.type_alias cx loc alias in
  Node_cache.set_alias cache loc (t, ast);
  t

let resolve_opaque_type cx loc opaque =
  let cache = Context.node_cache cx in
  let (t, ast) = Statement.opaque_type cx loc opaque in
  Node_cache.set_opaque cache loc (t, ast);
  t

let resolve_import cx id_loc import_reason import_kind module_name source_loc import =
  let module_name = Flow_import_specifier.userland module_name in
  let source_module =
    let import_kind_for_untyped_import_validation =
      match import_kind with
      | Ast.Statement.ImportDeclaration.ImportType -> Some Type.ImportType
      | Ast.Statement.ImportDeclaration.ImportTypeof -> Some Type.ImportTypeof
      | Ast.Statement.ImportDeclaration.ImportValue -> Some Type.ImportValue
    in
    Flow_js_utils.ImportExportUtils.get_module_type_or_any
      cx
      ~import_kind_for_untyped_import_validation
      (source_loc, module_name)
  in
  match import with
  | Name_def.Named { kind; remote; local } ->
    let import_kind = Base.Option.value ~default:import_kind kind in
    let (_, t) =
      Flow_js_utils.ImportExportUtils.import_named_specifier_type
        cx
        import_reason
        ~singleton_concretize_type_for_imports_exports:
          Flow_js.singleton_concretize_type_for_imports_exports
        ~import_kind
        ~module_name
        ~source_module
        ~remote_name:remote
        ~local_name:local
    in
    if Flow_ast_utils.hook_name local then
      make_hooklike cx t
    else
      t
  | Namespace name ->
    let t =
      Flow_js_utils.ImportExportUtils.import_namespace_specifier_type
        cx
        import_reason
        ~import_kind
        ~module_name
        ~namespace_symbol:(mk_namespace_symbol ~name ~def_loc:id_loc)
        ~source_module
        ~local_loc:id_loc
    in
    if Flow_ast_utils.hook_name name then
      make_hooklike cx t
    else
      t
  | Default local_name ->
    let (_, t) =
      Flow_js_utils.ImportExportUtils.import_default_specifier_type
        cx
        import_reason
        ~singleton_concretize_type_for_imports_exports:
          Flow_js.singleton_concretize_type_for_imports_exports
        ~import_kind
        ~module_name
        ~source_module
        ~local_name
    in
    if Flow_ast_utils.hook_name local_name then
      make_hooklike cx t
    else
      t

let resolve_interface cx loc inter =
  let cache = Context.node_cache cx in
  let (t, ast) = Statement.interface cx loc inter in
  Node_cache.set_interface cache loc (t, ast);
  t

let resolve_declare_class cx loc class_ =
  let cache = Context.node_cache cx in
  let (t, ast) = Statement.declare_class cx loc class_ in
  Node_cache.set_declared_class cache loc (t, ast);
  t

let resolve_declare_component cx loc component =
  let cache = Context.node_cache cx in
  let (t, ast) = Statement.declare_component cx loc component in
  Node_cache.set_declared_component cache loc (t, ast);
  t

let resolve_declare_namespace cx loc ns =
  let cache = Context.node_cache cx in
  let ((t, _) as ast) = Statement.declare_namespace cx loc ns in
  Node_cache.set_declared_namespace cache loc ast;
  t

let resolve_enum cx id_loc enum_reason enum_loc name enum =
  if Context.enable_enums cx then
    let enum_info = ConcreteEnum (Statement.mk_enum cx ~enum_reason id_loc name enum) in
    mk_enum_object_type enum_reason enum_info
  else (
    Flow_js_utils.add_output cx (Error_message.EEnumsNotEnabled enum_loc);
    AnyT.error enum_reason
  )

let resolve_type_param cx id_loc =
  let { Loc_env.tparams; _ } = Context.environment cx in
  let (_, _, t) = ALocMap.find id_loc tparams in
  DefT (TypeUtil.reason_of_t t, TypeT (TypeParamKind, t))

let resolve_chain_expression cx ~cond exp =
  let cache = Context.node_cache cx in
  let (t, _, exp) = Statement.optional_chain ~encl_ctx:cond cx exp in
  Node_cache.set_expression cache exp;
  t

let resolve_write_expression cx ~cond exp = synthesizable_expression cx ~encl_ctx:cond exp

let resolve_match_pattern cx def_reason case_match_root_loc ~has_guard ~prev_pattern_loc pattern =
  let (pattern_loc, _) : (ALoc.t, ALoc.t * Type.t) Ast.MatchPattern.t =
    Statement.match_pattern cx case_match_root_loc ~has_guard pattern
  in
  (* Set the match pattern union cache for incremental PatternUnion building.
     This allows subsequent patterns to depend on this one and build incrementally. *)
  let cache = Context.node_cache cx in
  let pattern_union_state = get_pattern_union_state_from_prev cache cx prev_pattern_loc in
  Node_cache.set_match_pattern_union cache pattern_loc pattern_union_state;
  MixedT.why def_reason

let resolve_generator_next cx reason gen =
  let open TypeUtil in
  match gen with
  | None -> VoidT.make (replace_desc_reason RUnannotatedNext reason)
  | Some { tparams_map; return_annot; async } ->
    let return_t =
      let cache = Context.node_cache cx in
      let tparams_map = mk_tparams_map cx tparams_map in
      let (t, anno) = Anno.mk_type_available_annotation cx tparams_map return_annot in
      Node_cache.set_annotation cache anno;
      t
    in
    let gen_name =
      if async then
        "AsyncGenerator"
      else
        "Generator"
    in
    Tvar.mk_where cx reason (fun next ->
        let t =
          Flow_js.get_builtin_typeapp
            cx
            reason
            gen_name
            [
              Tvar.mk cx (replace_desc_reason RUnusedYield reason);
              Tvar.mk cx (replace_desc_reason RUnusedReturn reason);
              next;
            ]
        in
        let t = Flow_js.reposition cx (reason_of_t return_t |> loc_of_reason) t in
        Flow_js.flow_t cx (t, return_t)
    )

let resolve cx (def_kind, id_loc) (def, def_scope_kind, class_stack, def_reason) =
  let env = Context.environment cx in
  Context.set_environment cx { env with Loc_env.scope_kind = def_scope_kind; class_stack };
  let t =
    match def with
    | Binding b -> resolve_binding cx def_scope_kind def_reason id_loc b
    | MatchCasePattern { case_match_root_loc; has_guard; pattern; prev_pattern_loc } ->
      resolve_match_pattern cx def_reason case_match_root_loc ~has_guard ~prev_pattern_loc pattern
    | ExpressionDef { cond_context = cond; expr; chain = true; hints = _ } ->
      resolve_chain_expression cx ~cond expr
    | ExpressionDef { cond_context = cond; expr; chain = false; hints = _ } ->
      resolve_write_expression cx ~cond expr
    | Component { component; component_loc; tparams_map } ->
      resolve_annotated_component cx def_scope_kind def_reason tparams_map component_loc component
    | Function
        {
          function_;
          synthesizable_from_annotation = FunctionSynthesizable;
          arrow;
          has_this_def = _;
          function_loc;
          tparams_map;
          statics;
          hints = _;
        } ->
      let hook_like = Base.Option.is_some (Flow_ast_utils.hook_function function_) in
      resolve_annotated_function
        cx
        ~scope_kind:def_scope_kind
        ~bind_this:(not arrow)
        ~statics
        ~hook_like
        def_reason
        tparams_map
        function_loc
        function_
    | Function
        {
          function_;
          synthesizable_from_annotation = _;
          arrow;
          has_this_def = _;
          function_loc;
          tparams_map = _;
          statics;
          hints = _;
        } ->
      resolve_inferred_function
        cx
        ~scope_kind:def_scope_kind
        ~statics
        ~needs_this_param:(not arrow)
        id_loc
        def_reason
        function_loc
        function_
    | Class { class_; class_loc; kind; this_super_write_locs = _ } ->
      resolve_class cx id_loc def_reason ~kind class_loc class_
    | Record { record; record_loc; this_super_write_locs = _; defaulted_props } ->
      resolve_record cx id_loc def_reason record_loc defaulted_props record
    | MemberAssign { member_loc = _; member = _; rhs } -> expression cx rhs
    | OpAssign { exp_loc; lhs; op; rhs; assertion } ->
      resolve_op_assign cx ~exp_loc lhs assertion op rhs
    | Update { exp_loc; op = _ } -> resolve_update cx ~id_loc ~exp_loc def_reason
    | TypeAlias (loc, alias) -> resolve_type_alias cx loc alias
    | OpaqueType (loc, opaque) -> resolve_opaque_type cx loc opaque
    | Import { import_kind; source; source_loc; import } ->
      resolve_import cx id_loc def_reason import_kind source source_loc import
    | Interface (loc, inter) -> resolve_interface cx loc inter
    | DeclaredClass (loc, class_) -> resolve_declare_class cx loc class_
    | DeclaredComponent (loc, comp) -> resolve_declare_component cx loc comp
    | Enum (enum_loc, name, enum) -> resolve_enum cx id_loc def_reason enum_loc name enum
    | TypeParam _ -> resolve_type_param cx id_loc
    | GeneratorNext gen -> resolve_generator_next cx def_reason gen
    | DeclaredNamespace (loc, ns) -> resolve_declare_namespace cx loc ns
    | MissingThisAnnot -> AnyT.at (AnyError None) id_loc
  in
  let add_array_or_object_literal_declaration_tracking =
    let rec has_array_or_object_without_hint = function
      | (_, (Ast.Expression.Array _ | Ast.Expression.Object _)) -> true
      | (_, Ast.Expression.Conditional { Ast.Expression.Conditional.consequent; alternate; _ }) ->
        has_array_or_object_without_hint consequent || has_array_or_object_without_hint alternate
      | _ -> false
    in
    match def with
    | Binding (Root (ObjectValue _)) -> true
    | Binding (Root (EmptyArray _)) -> true
    | Binding (Root (Value { hints = _; expr; decl_kind = Some _; as_const = _ }))
      when has_array_or_object_without_hint expr ->
      true
    | _ -> false
  in
  Debug_js.Verbose.print_if_verbose_lazy
    cx
    ( lazy
      [
        Printf.sprintf
          "Setting variable at %s to %s"
          (ALoc.debug_to_string id_loc)
          (Debug_js.dump_t cx t);
      ]
      );
  Type_env.resolve_env_entry
    cx
    t
    ~kind:def_kind
    ~add_array_or_object_literal_declaration_tracking
    id_loc

let entries_of_def graph (kind, loc) =
  let open Name_def_ordering in
  let acc = EnvSet.singleton (kind, loc) in
  let rec add_from_bindings acc = function
    | Root (UnannotatedParameter r) -> EnvSet.add (Env_api.FunctionParamLoc, loc_of_reason r) acc
    | Root (Annotation { param_loc = Some l; _ }) -> EnvSet.add (Env_api.FunctionParamLoc, l) acc
    | Root (Contextual { reason; _ }) ->
      let l = Reason.loc_of_reason reason in
      EnvSet.add (Env_api.FunctionParamLoc, l) acc
    | Root
        (FunctionValue
          {
            function_loc;
            arrow = false;
            function_ = { Ast.Function.params = (_, { Ast.Function.Params.this_ = None; _ }); _ };
            _;
          }
          ) ->
      EnvSet.add (Env_api.FunctionThisLoc, function_loc) acc
    | Root (ObjectValue { synthesizable = ObjectSynthesizable { this_write_locs }; _ }) ->
      EnvSet.union this_write_locs acc
    | Hooklike bind -> add_from_bindings acc bind
    | Root _ -> acc
    | Select _ -> acc
  in
  match EnvMap.find (kind, loc) graph with
  | (Binding b, _, _, _) -> add_from_bindings acc b
  | (Class { this_super_write_locs; _ }, _, _, _) -> EnvSet.union this_super_write_locs acc
  | (Record { this_super_write_locs; _ }, _, _, _) -> EnvSet.union this_super_write_locs acc
  | ( Function
        {
          has_this_def = true;
          function_loc;
          function_ = { Ast.Function.params = (_, { Ast.Function.Params.this_ = None; _ }); _ };
          _;
        },
      _,
      _,
      _
    ) ->
    EnvSet.add (Env_api.FunctionThisLoc, function_loc) acc
  | _ -> acc

let entries_of_component graph component =
  let open Name_def_ordering in
  let entries_of_elt element =
    let kl =
      match element with
      | Name_def_ordering.Normal kl
      | Resolvable kl
      | Illegal { payload = kl; _ } ->
        kl
    in
    entries_of_def graph kl
  in

  match component with
  | Singleton elt -> entries_of_elt elt
  | ResolvableSCC elts ->
    Nel.fold_left (fun acc def -> EnvSet.union acc (entries_of_elt def)) EnvSet.empty elts
  | IllegalSCC elts ->
    Nel.fold_left
      (fun acc ({ payload = elt; _ }, _) -> EnvSet.union acc (entries_of_elt elt))
      EnvSet.empty
      elts

let init_type_param =
  let rec init_type_param cx graph def_loc =
    let (def, _, _, reason) = EnvMap.find_ordinary def_loc graph in
    let tparam_entry =
      match def with
      | TypeParam { tparams_map = tparams_locs; kind; tparam } ->
        let tparams_map = mk_tparams_map cx graph tparams_locs in
        let ((_, ({ name; _ } as tparam), t) as info) =
          Anno.mk_type_param cx tparams_map ~kind tparam
        in
        let cache = Context.node_cache cx in
        Node_cache.set_tparam cache info;
        (name, tparam, t)
      | Class { class_loc; _ } ->
        let self = Type_env.read_class_self_type cx class_loc in
        let (this_param, this_t) = Statement.Class_stmt_sig.mk_this ~self cx reason in
        (Subst_name.Name "this", this_param, this_t)
      | Record { record_loc; _ } ->
        let self = Type_env.read_class_self_type cx record_loc in
        let (this_param, this_t) = Statement.Class_stmt_sig.mk_this ~self cx reason in
        (Subst_name.Name "this", this_param, this_t)
      | _ ->
        failwith
          (Utils_js.spf
             "tparam_locs contain a non-tparam location: %s"
             (ALoc.debug_to_string ~include_source:true def_loc)
          )
    in
    let ({ Loc_env.tparams; _ } as env) = Context.environment cx in
    Context.set_environment
      cx
      { env with Loc_env.tparams = ALocMap.add def_loc tparam_entry tparams };
    tparam_entry
  and get_type_param cx graph l =
    let { Loc_env.tparams; _ } = Context.environment cx in
    match ALocMap.find_opt l tparams with
    | Some entry -> entry
    | None -> init_type_param cx graph l
  and mk_tparams_map cx graph tparams_map =
    ALocMap.fold
      (fun l _ subst_map ->
        let (name, _, ty) = get_type_param cx graph l in
        Subst_name.Map.add name ty subst_map)
      tparams_map
      Subst_name.Map.empty
  in
  init_type_param

let resolve_component_type_params cx graph component =
  let open Name_def_ordering in
  let resolve_illegal loc def =
    match def with
    | ( TypeParam
          {
            tparam =
              ( _,
                {
                  Ast.Type.TypeParam.name =
                    (name_loc, { Ast.Identifier.name = str_name; comments = _ });
                  _;
                }
              );
            _;
          },
        _,
        _,
        _
      ) ->
      let name = Subst_name.Name str_name in
      let reason = mk_annot_reason (RType (OrdinaryName str_name)) name_loc in
      let tparam =
        {
          reason;
          name;
          bound = DefT (reason, MixedT Mixed_everything);
          polarity = Polarity.Neutral;
          default = None;
          is_this = false;
          is_const = false;
        }
      in
      let ({ Loc_env.tparams; _ } as env) = Context.environment cx in
      Context.set_environment
        cx
        {
          env with
          Loc_env.tparams = ALocMap.add loc (name, tparam, AnyT.at (AnyError None) loc) tparams;
        }
    | (Class _, _, _, _) ->
      let name = Subst_name.Name "this" in
      let reason = mk_annot_reason RThis loc in
      let tparam =
        {
          reason;
          name;
          bound = DefT (reason, MixedT Mixed_everything);
          polarity = Polarity.Neutral;
          default = None;
          is_this = true;
          is_const = false;
        }
      in
      let ({ Loc_env.tparams; _ } as env) = Context.environment cx in
      Context.set_environment
        cx
        {
          env with
          Loc_env.tparams = ALocMap.add loc (name, tparam, AnyT.at (AnyError None) loc) tparams;
        }
    | (Record _, _, _, _) ->
      let name = Subst_name.Name "this" in
      let reason = mk_annot_reason RThis loc in
      let tparam =
        {
          reason;
          name;
          bound = DefT (reason, MixedT Mixed_everything);
          polarity = Polarity.Neutral;
          default = None;
          is_this = true;
          is_const = false;
        }
      in
      let ({ Loc_env.tparams; _ } as env) = Context.environment cx in
      Context.set_environment
        cx
        {
          env with
          Loc_env.tparams = ALocMap.add loc (name, tparam, AnyT.at (AnyError None) loc) tparams;
        }
    | _ -> ()
  in
  let resolve_element = function
    | Illegal { payload = key; _ } ->
      let (_kind, loc) = key in
      resolve_illegal loc (EnvMap.find key graph)
    | Name_def_ordering.Normal key
    | Resolvable key ->
      (match EnvMap.find key graph with
      | (TypeParam _, _, _, _)
      | (Class _, _, _, _)
      | (Record _, _, _, _) ->
        let (_kind, loc) = key in
        ignore @@ init_type_param cx graph loc
      | _ -> ())
  in
  match component with
  | IllegalSCC elts ->
    Nel.iter
      (fun ( {
               payload =
                 Illegal { payload; _ } | Resolvable payload | Name_def_ordering.Normal payload;
               _;
             },
             _
           ) -> resolve_illegal (snd payload) (EnvMap.find payload graph))
      elts
  | Singleton elt -> resolve_element elt
  | ResolvableSCC elts -> Nel.iter (fun elt -> resolve_element elt) elts

let resolve_component cx graph component =
  let open Name_def_ordering in
  Context.constraint_cache cx := FlowSet.empty;
  Context.eval_repos_cache cx := EvalReposCacheMap.empty;
  let resolve_illegal entries =
    EnvSet.iter
      (fun (kind, loc) ->
        Type_env.resolve_env_entry
          cx
          (AnyT.at (AnyError None) loc)
          ~kind
          ~add_array_or_object_literal_declaration_tracking:false
          loc)
      entries
  in
  let resolve_element = function
    | Illegal { payload; _ } -> resolve_illegal (entries_of_def graph payload)
    | Name_def_ordering.Normal (kind, loc)
    | Resolvable (kind, loc) ->
      Abnormal.try_with_abnormal_exn
        ~f:(fun () -> resolve cx (kind, loc) (EnvMap.find (kind, loc) graph))
          (* When there is an unhandled exception, it means that the initialization of the env slot
             won't be completed and will never be written in the new-env, so it's OK to do nothing. *)
        ~on_abnormal_exn:(fun _ -> ())
        ()
  in
  Debug_js.Verbose.print_if_verbose_lazy
    cx
    (lazy [Utils_js.spf "Resolving component %s" (string_of_component graph component)]);
  let log_slow_to_check ~f =
    match Context.slow_to_check_logging cx with
    | { Slow_to_check_logging.slow_components_logging_threshold = Some threshold; _ } ->
      let start_time = Unix.gettimeofday () in
      let result = f () in
      let end_time = Unix.gettimeofday () in
      let run_time = end_time -. start_time in
      if run_time > threshold then
        Hh_logger.info
          "[%d] Slow CHECK component %s (%f seconds)"
          (Sys_utils.get_pretty_pid ())
          (string_of_component graph component)
          run_time;
      result
    | _ -> f ()
  in
  let f () =
    let entries_for_resolution =
      let entries = entries_of_component graph component in
      Type_env.make_env_entries_under_resolution cx entries;
      entries
    in
    resolve_component_type_params cx graph component;
    let () =
      match component with
      | IllegalSCC _ -> resolve_illegal entries_for_resolution
      | Singleton elt -> resolve_element elt
      | ResolvableSCC elts -> Nel.iter (fun elt -> resolve_element elt) elts
    in
    Debug_js.Verbose.print_if_verbose_lazy cx (lazy ["Finished resolving component"]);
    Debug_js.Verbose.print_if_verbose_lazy
      cx
      (lazy ["Forcing all lazy tvars after resolving component"]);
    let env = Context.environment cx in
    EnvSet.iter
      (fun (kind, loc) ->
        Loc_env.find_write env kind loc
        |> Base.Option.iter ~f:(fun (Loc_env.TypeEntry { t; state = _ }) ->
               match t with
               | OpenT (_, id) ->
                 (match Context.find_constraints cx id with
                 | (root_id, Type.Constraint.FullyResolved s) ->
                   Context.add_post_component_tvar_forcing_state cx root_id s
                 | _ -> ())
               | _ -> ()
           ))
      entries_for_resolution;
    Context.post_component_tvar_forcing_states cx
    |> Base.List.iter ~f:(fun s -> ignore @@ Context.force_fully_resolved_tvar cx s);
    Debug_js.Verbose.print_if_verbose_lazy
      cx
      (lazy ["Forced all lazy tvars after resolving component"])
  in
  log_slow_to_check ~f
