(*
 * Copyright (c) 2015, Facebook, Inc.
 * All rights reserved.
 *
 * This source code is licensed under the MIT license found in the
 * LICENSE file in the "hack" directory of this source tree.
 *
 *)

(* This module implements the typing.
 *
 * Given an Nast.program, it infers the type of all the local
 * variables, and checks that all the types are correct (aka
 * consistent) *)
open Hh_prelude
open Common
open Aast
open Tast
open Typing_defs
open Typing_env_types
open Utils
open Typing_helpers
module TFTerm = Typing_func_terminality
module TUtils = Typing_utils
module Reason = Typing_reason
module Type = Typing_ops
module Env = Typing_env
module Inf = Typing_inference_env
module LEnv = Typing_lenv
module Async = Typing_async
module SubType = Typing_subtype
module Union = Typing_union
module Inter = Typing_intersection
module SN = Naming_special_names
module TVis = Typing_visibility
module Phase = Typing_phase
module TOG = Typing_object_get
module Subst = Decl_subst
module ExprDepTy = Typing_dependent_type.ExprDepTy
module TCO = TypecheckerOptions
module C = Typing_continuations
module CMap = C.Map
module Try = Typing_try
module FL = FeatureLogging
module MakeType = Typing_make_type
module Cls = Decl_provider.Class
module Fake = Typing_fake_members
module ExpectedTy = Typing_helpers.ExpectedTy
module ITySet = Internal_type_set

type newable_class_info =
  env
  * Tast.targ list
  * Tast.class_id
  * [ `Class of pos_id * Cls.t * locl_ty | `Dynamic ] list

(*****************************************************************************)
(* Debugging *)
(*****************************************************************************)

(* A guess as to the last position we were typechecking, for use in debugging,
 * such as figuring out what a runaway hh_server thread is doing. Updated
 * only best-effort -- it's an approximation to point debugging in the right
 * direction, nothing more. *)
let debug_last_pos = ref Pos.none

let debug_print_last_pos _ =
  Hh_logger.info
    "Last typecheck pos: %s"
    (Pos.string (Pos.to_absolute !debug_last_pos))

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

let mk_hole ?(source = Aast.Typing) ((_, pos, _) as expr) ~ty_have ~ty_expect =
  if equal_locl_ty ty_have ty_expect then
    expr
  else
    (* if the hole is generated from typing, we leave the type unchanged,
       if it is a call to `[unsafe|enforced]_cast`, we give it the expected type
    *)
    let ty_hole =
      match source with
      | Aast.Typing -> ty_have
      | UnsafeCast _
      | EnforcedCast _ ->
        ty_expect
    in
    make_typed_expr pos ty_hole @@ Aast.Hole (expr, ty_have, ty_expect, source)

let hole_on_err (te : Tast.expr) ~err_opt =
  Option.value_map err_opt ~default:te ~f:(fun (ty_have, ty_expect) ->
      mk_hole te ~ty_have ~ty_expect)

(* When typing compound assignments we generate a 'fake' expression which
   desugars it to the operation on the rhs of the assignment. If there
   was a subtyping error, we end up with the Hole on the fake rhs
   rather than the original rhs. This function rewrites the
   desugared expression with the Hole in the correct place *)
let resugar_binop expr =
  match expr with
  | ( topt,
      p,
      Aast.(
        Binop
          ( _,
            te1,
            (_, _, Hole ((_, _, Binop (op, _, te2)), ty_have, ty_expect, source))
          )) ) ->
    let hte2 = mk_hole te2 ~ty_have ~ty_expect ~source in
    let te = Aast.Binop (Ast_defs.Eq (Some op), te1, hte2) in
    Some (topt, p, te)
  | (topt, p, Aast.Binop (_, te1, (_, _, Aast.Binop (op, _, te2)))) ->
    let te = Aast.Binop (Ast_defs.Eq (Some op), te1, te2) in
    Some (topt, p, te)
  | _ -> None

(* When recording subtyping or coercion errors for union and intersection types
   we need to look at the error for each element and then reconstruct any
   errors into a union or intersection. If there were no errors for any
   element, the result if also `Ok`; if there was an error for at least
   on element we have `Error` with list of actual and expected types *)
let fold_coercion_errs errs =
  List.fold_left errs ~init:(Ok []) ~f:(fun acc err ->
      match (acc, err) with
      | (Ok xs, Ok x) -> Ok (x :: xs)
      | (Ok xs, Error (x, y)) -> Error (x :: xs, y :: xs)
      | (Error (xs, ys), Ok x) -> Error (x :: xs, x :: ys)
      | (Error (xs, ys), Error (x, y)) -> Error (x :: xs, y :: ys))

let union_coercion_errs errs =
  Result.fold
    ~ok:(fun tys -> Ok (MakeType.union Reason.Rnone tys))
    ~error:(fun (acts, exps) ->
      Error (MakeType.union Reason.Rnone acts, MakeType.union Reason.Rnone exps))
  @@ fold_coercion_errs errs

let intersect_coercion_errs errs =
  Result.fold
    ~ok:(fun tys -> Ok (MakeType.intersection Reason.Rnone tys))
    ~error:(fun (acts, exps) ->
      Error
        ( MakeType.intersection Reason.Rnone acts,
          MakeType.intersection Reason.Rnone exps ))
  @@ fold_coercion_errs errs

(** Given the type of an argument that has been unpacked and typed against
    positional and variadic function parameter types, apply the subtyping /
    coercion errors back to the original packed type. *)
let pack_errs pos ty subtyping_errs =
  let nothing =
    MakeType.nothing @@ Reason.Rsolve_fail (Pos_or_decl.of_raw_pos pos)
  in
  let rec aux ~k = function
    (* Case 1: we have a type error at this positional parameter so
       replace the type parameter which caused it with the expected type *)
    | ((Some (_, ty) :: rest, var_opt), _ :: tys)
    (* Case 2: there was no type error here so retain the original type
       parameter *)
    | ((None :: rest, var_opt), ty :: tys) ->
      (* recurse for any remaining positional parameters and add the
         corrected (case 1) or original (case 2) type to the front of the
         list of type parameters in the continuation *)
      aux ((rest, var_opt), tys) ~k:(fun tys -> k (ty :: tys))
    (* Case 3: we have a type error at the variadic parameter so replace
       the type parameter which cased it with the expected type *)
    | ((_, (Some (_, ty) as var_opt)), _ :: tys) ->
      (* recurse with the variadic parameter error and add the
         corrected type to the front of the list of type parameters in the
         continuation *)
      aux (([], var_opt), tys) ~k:(fun tys -> k (ty :: tys))
    (* Case 4: we have a variadic parameter but no error - we're done so
       pass the remaining unchanged type parameters into the contination
       to rebuild corrected type params in the right order *)
    | ((_, None), tys) -> k tys
    (* Case 5: no more type parameters - again we're done so pass empty
       list to continuation and rebuild corrected type params in the right
       order *)
    | (_, []) -> k []
  in
  (* The only types that _can_ be upacked are tuples and pairs; match on the
     type to get the type parameters, pass them to our recursive function
     aux to subsitute the expected type where we have a type error
     then reconstruct the type in the continuation *)
  match deref ty with
  | (r, Ttuple tys) ->
    aux (subtyping_errs, tys) ~k:(fun tys -> mk (r, Ttuple tys))
  | (r, Tclass (pos_id, exact, tys)) ->
    aux (subtyping_errs, tys) ~k:(fun tys ->
        mk (r, Tclass (pos_id, exact, tys)))
  | _ -> nothing

let err_witness env p = TUtils.terr env (Reason.Rwitness p)

let triple_to_pair (env, te, ty) = (env, (te, ty))

let with_special_coeffects env cap_ty unsafe_cap_ty f =
  let init =
    Option.map (Env.next_cont_opt env) ~f:(fun next_cont ->
        let initial_locals = next_cont.Typing_per_cont_env.local_types in
        let tpenv = Env.get_tpenv env in
        (initial_locals, tpenv))
  in
  Typing_lenv.stash_and_do env (Env.all_continuations env) (fun env ->
      let env =
        match init with
        | None -> env
        | Some (initial_locals, tpenv) ->
          let env = Env.reinitialize_locals env in
          let env = Env.set_locals env initial_locals in
          let env = Env.env_with_tpenv env tpenv in
          env
      in
      let (env, _ty) =
        Typing_coeffects.register_capabilities env cap_ty unsafe_cap_ty
      in
      f env)

(* Set all the types in an expression to the given type. *)
let with_type ty env (e : Nast.expr) : Tast.expr =
  let visitor =
    object (self)
      inherit [_] Aast.map

      method! on_expr env ((), p, expr_) = (ty, p, self#on_expr_ env expr_)

      method on_'ex _ () = ty

      method on_'en _ _ = env
    end
  in
  visitor#on_expr () e

let invalid_expr_ env p : Tast.expr_ =
  let expr = ((), p, Naming.invalid_expr_ p) in
  let ty = TUtils.terr env Reason.Rnone in
  let (_, _, expr_) = with_type ty Tast.dummy_saved_env expr in
  expr_

let expr_error env (r : Reason.t) (e : Nast.expr) =
  let ty = TUtils.terr env r in
  (env, with_type ty Tast.dummy_saved_env e, ty)

let expr_any env p e =
  let ty = Typing_utils.mk_tany env p in
  (env, with_type ty Tast.dummy_saved_env e, ty)

let unbound_name env (pos, name) e =
  match Env.get_mode env with
  | FileInfo.Mstrict ->
    Errors.unbound_name_typing pos name;
    expr_error env (Reason.Rwitness pos) e
  | FileInfo.Mhhi -> expr_any env pos e

(* Is this type Traversable<vty> or Container<vty> for some vty? *)
let get_value_collection_inst env ty =
  match get_node ty with
  | Tclass ((_, c), _, [vty])
    when String.equal c SN.Collections.cTraversable
         || String.equal c SN.Collections.cContainer ->
    Some vty
  (* If we're expecting a mixed or a nonnull then we can just assume
   * that the element type is mixed *)
  | Tnonnull -> Some (MakeType.mixed Reason.Rnone)
  | Tany _ -> Some ty
  | Tdynamic when env.in_support_dynamic_type_method_check ->
    Some ty (* interpret dynamic as Traversable<dynamic> *)
  | _ -> None

(* Is this type KeyedTraversable<kty,vty>
 *           or KeyedContainer<kty,vty>
 * for some kty, vty?
 *)
let get_key_value_collection_inst env p ty =
  match get_node ty with
  | Tclass ((_, c), _, [kty; vty])
    when String.equal c SN.Collections.cKeyedTraversable
         || String.equal c SN.Collections.cKeyedContainer ->
    Some (kty, vty)
  (* If we're expecting a mixed or a nonnull then we can just assume
   * that the key type is arraykey and the value type is mixed *)
  | Tnonnull ->
    let arraykey = MakeType.arraykey (Reason.Rkey_value_collection_key p) in
    let mixed = MakeType.mixed Reason.Rnone in
    Some (arraykey, mixed)
  | Tany _ -> Some (ty, ty)
  | Tdynamic when env.in_support_dynamic_type_method_check ->
    (* interpret dynamic as KeyedTraversable<arraykey, dynamic> *)
    let arraykey = MakeType.arraykey (Reason.Rkey_value_collection_key p) in
    Some (arraykey, ty)
  | _ -> None

(* Is this type varray<vty> or a supertype for some vty? *)
let vc_kind_to_supers kind =
  match kind with
  | Vector -> [SN.Collections.cVector; SN.Collections.cMutableVector]
  | ImmVector -> [SN.Collections.cImmVector; SN.Collections.cConstVector]
  | Vec -> [SN.Collections.cVec]
  | Set -> [SN.Collections.cSet; SN.Collections.cMutableSet]
  | ImmSet -> [SN.Collections.cImmSet; SN.Collections.cConstSet]
  | Keyset -> [SN.Collections.cKeyset]

let kvc_kind_to_supers kind =
  match kind with
  | Map -> [SN.Collections.cMap; SN.Collections.cMutableMap]
  | ImmMap -> [SN.Collections.cImmMap; SN.Collections.cConstMap]
  | Dict -> [SN.Collections.cDict]

(* Is this type one of the value collection types with element type vty? *)
let get_vc_inst env vc_kind ty =
  let classnames = vc_kind_to_supers vc_kind in
  match get_node ty with
  | Tclass ((_, c), _, [vty]) when List.exists classnames ~f:(String.equal c) ->
    Some vty
  | _ -> get_value_collection_inst env ty

(* Is this type one of the three key-value collection types
 * e.g. dict<kty,vty> or a supertype for some kty and vty? *)
let get_kvc_inst env p kvc_kind ty =
  let classnames = kvc_kind_to_supers kvc_kind in
  match get_node ty with
  | Tclass ((_, c), _, [kty; vty])
    when List.exists classnames ~f:(String.equal c) ->
    Some (kty, vty)
  | _ -> get_key_value_collection_inst env p ty

(* Check whether this is a function type that (a) either returns a disposable
 * or (b) has the <<__ReturnDisposable>> attribute
 *)
let is_return_disposable_fun_type env ty =
  let (_env, ty) = Env.expand_type env ty in
  match get_node ty with
  | Tfun ft ->
    get_ft_return_disposable ft
    || Option.is_some
         (Typing_disposable.is_disposable_type env ft.ft_ret.et_type)
  | _ -> false

(* Turn an environment into a local_id_map suitable to be embedded
 * into an AssertEnv statement
 *)
let annot_map env =
  match Env.next_cont_opt env with
  | Some { Typing_per_cont_env.local_types; _ } ->
    Some (Local_id.Map.map (fun (ty, pos, _expr_id) -> (pos, ty)) local_types)
  | None -> None

(* Similar to annot_map above, but filters the map to only contain
 * information about locals in lset
 *)
let refinement_annot_map env lset =
  match annot_map env with
  | Some map ->
    let map =
      Local_id.Map.filter (fun lid _ -> Local_id.Set.mem lid lset) map
    in
    if Local_id.Map.is_empty map then
      None
    else
      Some map
  | None -> None

let assert_env_blk ~pos ~at annotation_kind env_map_opt blk =
  let mk_assert map = (pos, Aast.AssertEnv (annotation_kind, map)) in
  let annot_blk = Option.to_list (Option.map ~f:mk_assert env_map_opt) in
  match at with
  | `Start -> annot_blk @ blk
  | `End -> blk @ annot_blk

let assert_env_stmt ~pos ~at annotation_kind env_map_opt stmt =
  let mk_assert map = (pos, Aast.AssertEnv (annotation_kind, map)) in
  match env_map_opt with
  | Some env_map ->
    let stmt = (pos, stmt) in
    let blk =
      match at with
      | `Start -> [mk_assert env_map; stmt]
      | `End -> [stmt; mk_assert env_map]
    in
    Aast.Block blk
  | None -> stmt

let set_tcopt_unstable_features env { fa_user_attributes; _ } =
  match
    Naming_attributes.find
      SN.UserAttributes.uaEnableUnstableFeatures
      fa_user_attributes
  with
  | None -> env
  | Some { ua_name = _; ua_params } ->
    let ( = ) = String.equal in
    List.fold ua_params ~init:env ~f:(fun env (_, _, feature) ->
        match feature with
        | Aast.String s when s = SN.UnstableFeatures.ifc ->
          Env.map_tcopt ~f:TypecheckerOptions.enable_ifc env
        | Aast.String s when s = SN.UnstableFeatures.modules ->
          Env.map_tcopt ~f:(fun t -> TypecheckerOptions.set_modules t true) env
        | Aast.String s when s = SN.UnstableFeatures.expression_trees ->
          Env.map_tcopt
            ~f:(fun t ->
              TypecheckerOptions.set_tco_enable_expression_trees t true)
            env
        | _ -> env)

(** Do a subtype check of inferred type against expected type.
   * The optional coerce_for_op parameter controls whether any arguments of type
   * dynamic can be coerced to enforceable types because they are arguments to a
   * built-in operator.
 *)
let check_expected_ty_res
    ~(coerce_for_op : bool)
    (message : string)
    (env : env)
    (inferred_ty : locl_ty)
    (expected : ExpectedTy.t option) : (env, env) result =
  match expected with
  | None -> Ok env
  | Some ExpectedTy.{ pos = p; reason = ur; ty } ->
    Typing_log.(
      log_with_level env "typing" ~level:1 (fun () ->
          log_types
            (Pos_or_decl.of_raw_pos p)
            env
            [
              Log_head
                ( Printf.sprintf
                    "Typing.check_expected_ty %s enforced=%s"
                    message
                    (match ty.et_enforced with
                    | Unenforced -> "unenforced"
                    | Enforced -> "enforced"),
                  [
                    Log_type ("inferred_ty", inferred_ty);
                    Log_type ("expected_ty", ty.et_type);
                  ] );
            ]));
    Typing_coercion.coerce_type_res
      ~coerce_for_op
      p
      ur
      env
      inferred_ty
      ty
      Errors.unify_error

let check_expected_ty message env inferred_ty expected =
  Result.fold ~ok:Fn.id ~error:Fn.id
  @@ check_expected_ty_res ~coerce_for_op:false message env inferred_ty expected

(* Set a local; must not be already assigned if it is a using variable *)
let set_local ?(is_using_clause = false) env (pos, x) ty =
  if Env.is_using_var env x then
    if is_using_clause then
      Errors.duplicate_using_var pos
    else
      Errors.illegal_disposable pos "assigned";
  let env = Env.set_local env x ty pos in
  if is_using_clause then
    Env.set_using_var env x
  else
    env

(* Require a new construct with disposable *)
let rec enforce_return_disposable _env e =
  match e with
  | (_, _, New _) -> ()
  | (_, _, Call _) -> ()
  | (_, _, Await (_, _, Call _)) -> ()
  | (_, _, Hole (e, _, _, _)) -> enforce_return_disposable _env e
  | (_, p, _) -> Errors.invalid_return_disposable p

(* Wrappers around the function with the same name in Typing_lenv, which only
 * performs the move/save and merge operation if we are in a try block or in a
 * function with return type 'noreturn'.
 * This enables significant perf improvement, because this is called at every
 * function of method call, when most calls are outside of a try block. *)
let move_and_merge_next_in_catch env =
  if env.in_try || TFTerm.is_noreturn env then
    LEnv.move_and_merge_next_in_cont env C.Catch
  else
    LEnv.drop_cont env C.Next

let save_and_merge_next_in_catch env =
  if env.in_try || TFTerm.is_noreturn env then
    LEnv.save_and_merge_next_in_cont env C.Catch
  else
    env

let might_throw env = save_and_merge_next_in_catch env

let branch :
    type res. env -> (env -> env * res) -> (env -> env * res) -> env * res * res
    =
 fun env branch1 branch2 ->
  let parent_lenv = env.lenv in
  let (env, tbr1) = branch1 env in
  let lenv1 = env.lenv in
  let env = { env with lenv = parent_lenv } in
  let (env, tbr2) = branch2 env in
  let lenv2 = env.lenv in
  let env = LEnv.union_lenvs env parent_lenv lenv1 lenv2 in
  (env, tbr1, tbr2)

let as_expr env ty1 pe e =
  let env = Env.open_tyvars env pe in
  let (env, tv) = Env.fresh_type env pe in
  let (env, expected_ty, tk, tv) =
    match e with
    | As_v _ ->
      let tk = MakeType.mixed Reason.Rnone in
      (env, MakeType.traversable (Reason.Rforeach pe) tv, tk, tv)
    | As_kv _ ->
      let (env, tk) = Env.fresh_type env pe in
      (env, MakeType.keyed_traversable (Reason.Rforeach pe) tk tv, tk, tv)
    | Await_as_v _ ->
      let tk = MakeType.mixed Reason.Rnone in
      (env, MakeType.async_iterator (Reason.Rasyncforeach pe) tv, tk, tv)
    | Await_as_kv _ ->
      let (env, tk) = Env.fresh_type env pe in
      ( env,
        MakeType.async_keyed_iterator (Reason.Rasyncforeach pe) tk tv,
        tk,
        tv )
  in
  let rec distribute_union env ty =
    let (env, ty) = Env.expand_type env ty in
    match get_node ty with
    | Tunion tyl ->
      let (env, errs) =
        List.fold tyl ~init:(env, []) ~f:(fun (env, errs) ty ->
            let (env, err) = distribute_union env ty in
            (env, err :: errs))
      in
      (env, union_coercion_errs errs)
    | _ ->
      if SubType.is_sub_type_for_union env ty (MakeType.dynamic Reason.Rnone)
      then
        let env = SubType.sub_type env ty tk (Errors.unify_error_at pe) in
        let env = SubType.sub_type env ty tv (Errors.unify_error_at pe) in
        (env, Ok ty)
      else
        let ur = Reason.URforeach in
        let (env, err) =
          Result.fold
            ~ok:(fun env -> (env, Ok ty))
            ~error:(fun env -> (env, Error (ty, expected_ty)))
          @@ Type.sub_type_res pe ur env ty expected_ty Errors.unify_error
        in
        (env, err)
  in

  let (env, err_res) = distribute_union env ty1 in
  let err_opt =
    match err_res with
    | Ok _ -> None
    | Error (act, exp) -> Some (act, exp)
  in
  let env = Env.set_tyvar_variance env expected_ty in
  (Typing_solver.close_tyvars_and_solve env, tk, tv, err_opt)

(* These functions invoke special printing functions for Typing_env. They do not
 * appear in user code, but we still check top level function calls against their
 * names. *)
let typing_env_pseudofunctions =
  SN.PseudoFunctions.(
    String.Hash_set.of_list
      ~growth_allowed:false
      [
        hh_show;
        hh_expect;
        hh_expect_equivalent;
        hh_show_env;
        hh_log_level;
        hh_force_solve;
        hh_loop_forever;
      ])

let do_hh_expect ~equivalent env use_pos explicit_targs p tys =
  match explicit_targs with
  | [targ] ->
    let (env, expected_ty) =
      Phase.localize_targ ~check_well_kinded:true env (snd targ)
    in
    (match tys with
    | [expr_ty] ->
      let res =
        SubType.sub_type_res
          env
          expr_ty
          (fst expected_ty)
          (Errors.hh_expect_error ~equivalent p)
      in
      (match res with
      | Ok env ->
        if equivalent then
          SubType.sub_type
            env
            (fst expected_ty)
            expr_ty
            (Errors.hh_expect_error ~equivalent p)
        else
          env
      | Error env -> env)
    | _ -> env)
  | _ ->
    Errors.expected_tparam ~definition_pos:Pos_or_decl.none ~use_pos 1 None;
    env

let loop_forever env =
  (* forever = up to 10 minutes, to avoid accidentally stuck processes *)
  for i = 1 to 600 do
    (* Look up things in shared memory occasionally to have a chance to be
     * interrupted *)
    match Env.get_class env "FOR_TEST_ONLY" with
    | None -> Unix.sleep 1
    | _ -> assert false
  done;
  Utils.assert_false_log_backtrace
    (Some "hh_loop_forever was looping for more than 10 minutes")

let is_parameter env x = Local_id.Map.mem x (Env.get_params env)

let check_escaping_var env (pos, x) =
  if Env.is_using_var env x then
    if Local_id.equal x this then
      Errors.escaping_this pos
    else if is_parameter env x then
      Errors.escaping_disposable_parameter pos
    else
      Errors.escaping_disposable pos
  else
    ()

let make_result env p te ty =
  (* Set the variance of any type variables that were generated according
   * to how they appear in the expression type *)
  let env = Env.set_tyvar_variance env ty in
  (env, Tast.make_typed_expr p ty te, ty)

let localize_targ env ta =
  let pos = fst ta in
  let (env, targ) = Phase.localize_targ ~check_well_kinded:true env ta in
  (env, targ, ExpectedTy.make pos Reason.URhint (fst targ))

let set_function_pointer ty =
  match get_node ty with
  | Tfun ft ->
    let ft = set_ft_is_function_pointer ft true in
    mk (get_reason ty, Tfun ft)
  | _ -> ty

let xhp_attribute_decl_ty env sid obj attr =
  let (namepstr, valpty) = attr in
  let (valp, valty) = valpty in
  let (env, (declty, _tal)) =
    TOG.obj_get
      ~obj_pos:(fst sid)
      ~is_method:false
      ~inst_meth:false
      ~meth_caller:false
      ~nullsafe:None
      ~coerce_from_ty:None
      ~explicit_targs:[]
      ~class_id:(CI sid)
      ~member_id:namepstr
      ~on_error:Errors.unify_error
      env
      obj
  in
  let ureason = Reason.URxhp (snd sid, snd namepstr) in
  let (env, err_opt) =
    Result.fold
      ~ok:(fun env -> (env, None))
      ~error:(fun env -> (env, Some (valty, declty)))
    @@ Typing_coercion.coerce_type_res
         valp
         ureason
         env
         valty
         (MakeType.unenforced declty)
         Errors.xhp_attribute_does_not_match_hint
  in
  (env, declty, err_opt)

let closure_check_param env param =
  match hint_of_type_hint param.param_type_hint with
  | None -> env
  | Some hty ->
    let hint_pos = fst hty in
    let (env, hty) =
      Phase.localize_hint_no_subst env ~ignore_errors:false hty
    in
    let paramty = Env.get_local env (Local_id.make_unscoped param.param_name) in
    let env =
      Typing_coercion.coerce_type
        hint_pos
        Reason.URhint
        env
        paramty
        (MakeType.unenforced hty)
        Errors.unify_error
    in
    env

let stash_conts_for_closure env p is_anon captured f =
  let captured =
    if is_anon && TypecheckerOptions.any_coeffects (Env.get_tcopt env) then
      Typing_coeffects.(
        (Pos.none, local_capability_id) :: (Pos.none, capability_id) :: captured)
    else
      captured
  in
  let captured =
    if Env.is_local_defined env this && not (Env.is_in_expr_tree env) then
      (Pos.none, this) :: captured
    else
      captured
  in
  let init =
    Option.map (Env.next_cont_opt env) ~f:(fun next_cont ->
        let initial_locals =
          if is_anon then
            Env.get_locals env captured
          else
            next_cont.Typing_per_cont_env.local_types
        in
        let initial_fakes =
          Fake.forget (Env.get_fake_members env) Reason.(Blame (p, BSlambda))
        in
        let tpenv = Env.get_tpenv env in
        (initial_locals, initial_fakes, tpenv))
  in
  Typing_lenv.stash_and_do env (Env.all_continuations env) (fun env ->
      let env =
        match init with
        | None -> env
        | Some (initial_locals, initial_fakes, tpenv) ->
          let env = Env.reinitialize_locals env in
          let env = Env.set_locals env initial_locals in
          let env = Env.set_fake_members env initial_fakes in
          let env = Env.env_with_tpenv env tpenv in
          env
      in
      f env)

let requires_consistent_construct = function
  | CIstatic -> true
  | CIexpr _ -> true
  | CIparent -> false
  | CIself -> false
  | CI _ -> false

(* Caller will be looking for a particular form of expected type
 * e.g. a function type (when checking lambdas) or tuple type (when checking
 * tuples). First expand the expected type and elide single union; also
 * strip nullables, so ?t becomes t, as context will always accept a t if a ?t
 * is expected.
 *
 * Note: we currently do not generally expand ?t into (null | t), so ~?t is (dynamic | Toption t).
 *)
let expand_expected_and_get_node env (expected : ExpectedTy.t option) =
  let rec unbox ty =
    match get_node ty with
    | Tunion [ty1; ty2] when is_dynamic ty1 -> unbox ty2
    | Tunion [ty1; ty2] when is_dynamic ty2 -> unbox ty1
    | Tunion [ty] -> unbox ty
    | Toption ty -> unbox ty
    | _ -> ty
  in
  match expected with
  | None -> (env, None)
  | Some ExpectedTy.{ pos = p; reason = ur; ty = { et_type = ty; _ }; _ } ->
    let (env, ty) = Env.expand_type env ty in
    let uty = unbox ty in
    (env, Some (p, ur, uty, get_node uty))

let uninstantiable_error env reason_pos cid c_tc_pos c_name c_usage_pos c_ty =
  let reason =
    match cid with
    | CIexpr _ ->
      let ty_str = "This would be " ^ Typing_print.error env c_ty in
      Some (reason_pos, ty_str)
    | _ -> None
  in
  Errors.uninstantiable_class c_usage_pos c_tc_pos c_name reason

let coerce_to_throwable pos env exn_ty =
  let throwable_ty = MakeType.throwable (Reason.Rthrow pos) in
  Typing_coercion.coerce_type
    pos
    Reason.URthrow
    env
    exn_ty
    { et_type = throwable_ty; et_enforced = Unenforced }
    Errors.unify_error

let set_valid_rvalue p env x ty =
  let env = set_local env (p, x) ty in
  (* We are assigning a new value to the local variable, so we need to
   * generate a new expression id
   *)
  Env.set_local_expr_id env x (Ident.tmp ())

let is_hack_collection env ty =
  (* TODO(like types) This fails if a collection is used as a parameter under
   * pessimization, because ~Vector<int> </: ConstCollection<mixed>. This is the
   * test we use to see whether to update the expression id for expressions
   * $vector[0] = $x and $vector[] = $x. If this is false, the receiver is assumed
   * to be a Hack array which are COW. This approximation breaks down in the presence
   * of dynamic. It is unclear whether we should change an expression id if the
   * receiver is dynamic. *)
  Typing_solver.is_sub_type
    env
    ty
    (MakeType.const_collection Reason.Rnone (MakeType.mixed Reason.Rnone))

let check_class_get env p def_pos cid mid ce (_, _cid_pos, e) function_pointer =
  match e with
  | CIself when get_ce_abstract ce ->
    begin
      match Env.get_self_id env with
      | Some self ->
        (* at runtime, self:: in a trait is a call to whatever
         * self:: is in the context of the non-trait "use"-ing
         * the trait's code *)
        begin
          match Env.get_class env self with
          | Some cls when Ast_defs.is_c_trait (Cls.kind cls) ->
            (* Ban self::some_abstract_method() in a trait, if the
             * method is also defined in a trait.
             *
             * Abstract methods from interfaces are fine: we'll check
             * in the child class that we actually have an
             * implementation. *)
            (match Decl_provider.get_class (Env.get_ctx env) ce.ce_origin with
            | Some meth_cls when Ast_defs.is_c_trait (Cls.kind meth_cls) ->
              Errors.self_abstract_call mid _cid_pos p def_pos
            | _ -> ())
          | _ ->
            (* Ban self::some_abstract_method() in a class. This will
             *  always error. *)
            Errors.self_abstract_call mid _cid_pos p def_pos
        end
      | None -> ()
    end
  | CIparent when get_ce_abstract ce ->
    Errors.parent_abstract_call mid p def_pos
  | CI _ when get_ce_abstract ce && function_pointer ->
    Errors.abstract_function_pointer cid mid p def_pos
  | CI _ when get_ce_abstract ce ->
    Errors.classname_abstract_call cid mid p def_pos
  | CI (_, classname) when get_ce_synthesized ce ->
    Errors.static_synthetic_method classname mid p def_pos
  | _ -> ()

(** Given an identifier for a function, find its function type in the
 * environment and localise it with the input type parameters. If the function
 * cannot be found, return [Terr].
 *)
let fun_type_of_id env x tal el =
  match Env.get_fun env (snd x) with
  | None ->
    let (env, _, ty) = unbound_name env x ((), Pos.none, Aast.Null) in
    (env, ty, [])
  | Some
      {
        fe_type;
        fe_pos;
        fe_deprecated;
        fe_support_dynamic_type;
        fe_internal;
        fe_module;
        _;
      } ->
    (match get_node fe_type with
    | Tfun ft ->
      let ft =
        let pessimise =
          TypecheckerOptions.pessimise_builtins (Env.get_tcopt env)
        in
        Typing_special_fun.transform_special_fun_ty
          ~pessimise
          ft
          x
          (List.length el)
      in
      let ety_env = empty_expand_env in
      let (env, tal) =
        Phase.localize_targs
          ~check_well_kinded:true
          ~is_method:true
          ~def_pos:fe_pos
          ~use_pos:(fst x)
          ~use_name:(strip_ns (snd x))
          env
          ft.ft_tparams
          (List.map ~f:snd tal)
      in
      let ft =
        Typing_enforceability.compute_enforced_and_pessimize_fun_type env ft
      in
      let use_pos = fst x in
      let def_pos = fe_pos in
      let (env, ft) =
        Phase.(
          localize_ft
            ~instantiation:
              { use_name = strip_ns (snd x); use_pos; explicit_targs = tal }
            ~def_pos
            ~ety_env
            env
            ft)
      in
      let fty =
        Typing_dynamic.relax_method_type
          env
          fe_support_dynamic_type
          (get_reason fe_type)
          ft
      in
      TVis.check_deprecated ~use_pos ~def_pos fe_deprecated;
      (if fe_internal then
        match
          Typing_modules.can_access
            ~current:(Env.get_module env)
            ~target:fe_module
        with
        | `Yes -> ()
        | `Disjoint (current, target) ->
          Errors.module_mismatch (fst x) fe_pos (Some current) target
        | `Outside target -> Errors.module_mismatch (fst x) fe_pos None target);
      (env, fty, tal)
    | _ -> failwith "Expected function type")

(**
 * Checks if a class (given by cty) contains a given static method.
 *
 * We could refactor this + class_get
 *)
let class_contains_smethod env cty (_pos, mid) =
  let lookup_member ty =
    match get_class_type ty with
    | Some ((_, c), _, _) ->
      (match Env.get_class env c with
      | None -> false
      | Some class_ ->
        Option.is_some @@ Env.get_static_member true env class_ mid)
    | None -> false
  in
  let (_env, tyl) =
    TUtils.get_concrete_supertypes ~abstract_enum:true env cty
  in
  List.exists tyl ~f:lookup_member

(* To be a valid trait declaration, all of its 'require extends' must
 * match; since there's no multiple inheritance, it follows that all of
 * the 'require extends' must belong to the same inheritance hierarchy
 * and one of them should be the child of all the others *)
let trait_most_concrete_req_class trait env =
  List.fold
    (Cls.all_ancestor_reqs trait)
    ~f:
      begin
        fun acc (_p, ty) ->
        let (_r, (_p, name), _paraml) = TUtils.unwrap_class_type ty in
        let keep =
          match acc with
          | Some (c, _ty) -> Cls.has_ancestor c name
          | None -> false
        in
        if keep then
          acc
        else
          let class_ = Env.get_class env name in
          match class_ with
          | None -> acc
          | Some c when Ast_defs.is_c_interface (Cls.kind c) -> acc
          | Some c when Ast_defs.is_c_trait (Cls.kind c) ->
            (* this is an error case for which Typing_type_wellformedness spits out
             * an error, but does *not* currently remove the offending
             * 'require extends' or 'require implements' *)
            acc
          | Some c -> Some (c, ty)
      end
    ~init:None

let check_arity ?(did_unpack = false) pos pos_def ft (arity : int) =
  let exp_min = Typing_defs.arity_min ft in
  if arity < exp_min then Errors.typing_too_few_args exp_min arity pos pos_def;
  match ft.ft_arity with
  | Fstandard ->
    let exp_max = List.length ft.ft_params in
    let arity =
      if did_unpack then
        arity + 1
      else
        arity
    in
    if arity > exp_max then
      Errors.typing_too_many_args exp_max arity pos pos_def
  | Fvariadic _ -> ()

let check_lambda_arity lambda_pos def_pos lambda_ft expected_ft =
  match (lambda_ft.ft_arity, expected_ft.ft_arity) with
  | (Fstandard, Fstandard) ->
    let expected_min = Typing_defs.arity_min expected_ft in
    let lambda_min = Typing_defs.arity_min lambda_ft in
    if lambda_min < expected_min then
      Errors.typing_too_few_args expected_min lambda_min lambda_pos def_pos;
    if lambda_min > expected_min then
      Errors.typing_too_many_args expected_min lambda_min lambda_pos def_pos
  | (_, _) -> ()

(* The variadic capture argument is an array listing the passed
 * variable arguments for the purposes of the function body; callsites
 * should not unify with it *)
let variadic_param env ft =
  match ft.ft_arity with
  | Fvariadic param -> (env, Some param)
  | Fstandard -> (env, None)

let param_modes
    ?(is_variadic = false) ({ fp_pos; _ } as fp) (_, pos, _) param_kind =
  match (get_fp_mode fp, param_kind) with
  | (FPnormal, Ast_defs.Pnormal) -> ()
  | (FPinout, Ast_defs.Pinout _) -> ()
  | (FPnormal, Ast_defs.Pinout p) ->
    Errors.inout_annotation_unexpected (Pos.merge p pos) fp_pos is_variadic
  | (FPinout, Ast_defs.Pnormal) -> Errors.inout_annotation_missing pos fp_pos

let split_remaining_params_required_optional ft remaining_params =
  (* Same example as above
   *
   * function f(int $i, string $j, float $k = 3.14, mixed ...$m): void {}
   * function g((string, float, bool) $t): void {
   *   f(3, ...$t);
   * }
   *
   * `remaining_params` will contain [string, float] and there has been 1 parameter consumed. The min_arity
   * of this function is 2, so there is 1 required parameter remaining and 1 optional parameter.
   *)
  let min_arity =
    List.count
      ~f:(fun fp -> not (Typing_defs.get_fp_has_default fp))
      ft.ft_params
  in
  let original_params = ft.ft_params in
  let consumed = List.length original_params - List.length remaining_params in
  let required_remaining = Int.max (min_arity - consumed) 0 in
  let (required_params, optional_params) =
    List.split_n remaining_params required_remaining
  in
  (consumed, required_params, optional_params)

let generate_splat_type_vars
    env p required_params optional_params variadic_param =
  let (env, d_required) =
    List.map_env env required_params ~f:(fun env _ -> Env.fresh_type env p)
  in
  let (env, d_optional) =
    List.map_env env optional_params ~f:(fun env _ -> Env.fresh_type env p)
  in
  let (env, d_variadic) =
    match variadic_param with
    | None -> (env, None)
    | Some _ ->
      let (env, ty) = Env.fresh_type env p in
      (env, Some ty)
  in
  (env, (d_required, d_optional, d_variadic))

let call_param
    env
    param
    param_kind
    (((_, pos, expr_) as e : Nast.expr), arg_ty)
    ~is_variadic : env * (locl_ty * locl_ty) option =
  param_modes ~is_variadic param e param_kind;
  (* When checking params, the type 'x' may be expression dependent. Since
   * we store the expression id in the local env for Lvar, we want to apply
   * it in this case.
   *)
  let (env, dep_ty) =
    match expr_ with
    | Hole ((_, _, Lvar _), _, _, _)
    | Lvar _ ->
      ExprDepTy.make env ~cid:(CIexpr e) arg_ty
    | _ -> (env, arg_ty)
  in
  let pos =
    match param_kind with
    | Ast_defs.Pnormal -> pos
    | Ast_defs.Pinout pk_pos -> Pos.merge pk_pos pos
  in
  Result.fold
    ~ok:(fun env -> (env, None))
    ~error:(fun env -> (env, Some (dep_ty, param.fp_type.et_type)))
  @@ Typing_coercion.coerce_type_res
       pos
       Reason.URparam
       env
       dep_ty
       param.fp_type
       (fun ?code ?quickfixes claim reasons ->
         if env.in_support_dynamic_type_method_check then
           Typing_log.log_pessimise_param env param.fp_pos param.fp_name;
         Errors.unify_error ?code ?quickfixes claim reasons)

let bad_call env p ty = Errors.bad_call p (Typing_print.error env ty)

let rec make_a_local_of ~include_this env e =
  match e with
  | (_, p, Class_get ((_, _, cname), CGstring (_, member_name), _)) ->
    let (env, local) = Env.FakeMembers.make_static env cname member_name p in
    (env, Some (p, local))
  | ( _,
      p,
      Obj_get
        ( (((_, _, This) | (_, _, Lvar _)) as obj),
          (_, _, Id (_, member_name)),
          _,
          _ ) ) ->
    let (env, local) = Env.FakeMembers.make env obj member_name p in
    (env, Some (p, local))
  | (_, _, Lvar x)
  | (_, _, Dollardollar x) ->
    (env, Some x)
  | (_, p, This) when include_this -> (env, Some (p, this))
  | (_, _, Hole (e, _, _, _)) -> make_a_local_of ~include_this env e
  | _ -> (env, None)

(* This function captures the common bits of logic behind refinement
 * of the type of a local variable or a class member variable as a
 * result of a dynamic check (e.g., nullity check, simple type check
 * using functions like is_int, is_string, is_array etc.).  The
 * argument refine is a function that takes the type of the variable
 * and returns a refined type (making necessary changes to the
 * environment, which is threaded through).
 *
 * All refinement functions return, in addition to the updated
 * environment, a (conservative) set of all the locals that got
 * refined. This set is used to construct AssertEnv statmements in
 * the typed AST.
 *)
let refine_lvalue_type env ((ty, _, _) as te) ~refine =
  let (env, ty) = refine env ty in
  let e = Tast.to_nast_expr te in
  let (env, localopt) = make_a_local_of ~include_this:false env e in
  (* TODO TAST: generate an assignment to the fake local in the TAST *)
  match localopt with
  | Some lid -> (set_local env lid ty, Local_id.Set.singleton (snd lid))
  | None -> (env, Local_id.Set.empty)

let rec condition_nullity ~nonnull (env : env) te =
  match te with
  (* assignment: both the rhs and lhs of the '=' must be made null/non-null *)
  | (_, _, Aast.Binop (Ast_defs.Eq None, var, te)) ->
    let (env, lset1) = condition_nullity ~nonnull env te in
    let (env, lset2) = condition_nullity ~nonnull env var in
    (env, Local_id.Set.union lset1 lset2)
  (* case where `Shapes::idx(...)` must be made null/non-null *)
  | ( _,
      _,
      Aast.Call
        ( (_, _, Aast.Class_const ((_, _, Aast.CI (_, shapes)), (_, idx))),
          _,
          [(Ast_defs.Pnormal, shape); (Ast_defs.Pnormal, field)],
          _ ) )
    when String.equal shapes SN.Shapes.cShapes && String.equal idx SN.Shapes.idx
    ->
    let field = Tast.to_nast_expr field in
    let refine env shape_ty =
      if nonnull then
        Typing_shapes.shapes_idx_not_null env shape_ty field
      else
        (env, shape_ty)
    in
    refine_lvalue_type env shape ~refine
  | (_, _, Hole (te, _, _, _)) -> condition_nullity ~nonnull env te
  | (_, p, _) ->
    let refine env ty =
      if nonnull then
        Typing_solver.non_null env (Pos_or_decl.of_raw_pos p) ty
      else
        let r = Reason.Rwitness_from_decl (get_pos ty) in
        Inter.intersect env ~r ty (MakeType.null r)
    in
    refine_lvalue_type env te ~refine

(** If we are dealing with a refinement like
      $x is MyClass<A, B>
    then class_info is the class info of MyClass and hint_tyl corresponds
    to A, B. *)
let generate_fresh_tparams env class_info p reason hint_tyl =
  let tparams_len = List.length (Cls.tparams class_info) in
  let hint_tyl = List.take hint_tyl tparams_len in
  let pad_len = tparams_len - List.length hint_tyl in
  let hint_tyl =
    List.map hint_tyl ~f:(fun x -> Some x)
    @ List.init pad_len ~f:(fun _ -> None)
  in
  let replace_wildcard env hint_ty tp =
    let {
      tp_name = (_, tparam_name);
      tp_reified = reified;
      tp_user_attributes;
      _;
    } =
      tp
    in
    let enforceable =
      Attributes.mem SN.UserAttributes.uaEnforceable tp_user_attributes
    in
    let newable =
      Attributes.mem SN.UserAttributes.uaNewable tp_user_attributes
    in
    match hint_ty with
    | Some ty ->
      begin
        match get_node ty with
        | Tgeneric (name, _targs) when Env.is_fresh_generic_parameter name ->
          (* TODO(T69551141) handle type arguments above and below *)
          (env, (Some (tp, name), MakeType.generic reason name))
        | _ -> (env, (None, ty))
      end
    | None ->
      let (env, new_name) =
        Env.add_fresh_generic_parameter
          env
          (Pos_or_decl.of_raw_pos p)
          tparam_name
          ~reified
          ~enforceable
          ~newable
      in
      (* TODO(T69551141) handle type arguments for Tgeneric *)
      (env, (Some (tp, new_name), MakeType.generic reason new_name))
  in
  let (env, tparams_and_tyl) =
    List.map2_env env hint_tyl (Cls.tparams class_info) ~f:replace_wildcard
  in
  let (tparams_with_new_names, tyl_fresh) = List.unzip tparams_and_tyl in
  (env, tparams_with_new_names, tyl_fresh)

let safely_refine_class_type
    env
    p
    class_name
    class_info
    ivar_ty
    obj_ty
    reason
    (tparams_with_new_names : (decl_tparam * string) option list)
    tyl_fresh =
  (* Type of variable in block will be class name
   * with fresh type parameters *)
  let obj_ty =
    mk (get_reason obj_ty, Tclass (class_name, Nonexact, tyl_fresh))
  in
  let tparams = Cls.tparams class_info in
  (* Add in constraints as assumptions on those type parameters *)
  let ety_env =
    {
      empty_expand_env with
      substs = Subst.make_locl tparams tyl_fresh;
      this_ty = obj_ty;
    }
  in
  let add_bounds env (t, ty_fresh) =
    List.fold_left t.tp_constraints ~init:env ~f:(fun env (ck, ty) ->
        (* Substitute fresh type parameters for
         * original formals in constraint *)
        let (env, ty) = Phase.localize ~ety_env env ty in
        SubType.add_constraint env ck ty_fresh ty (Errors.unify_error_at p))
  in
  let env =
    List.fold_left (List.zip_exn tparams tyl_fresh) ~f:add_bounds ~init:env
  in
  (* Finally, if we have a class-test on something with static classish type,
   * then we can chase the hierarchy and decompose the types to deduce
   * further assumptions on type parameters. For example, we might have
   *   class B<Tb> { ... }
   *   class C extends B<int>
   * and have obj_ty = C and x_ty = B<T> for a generic parameter Aast.
   * Then SubType.add_constraint will deduce that T=int and add int as
   * both lower and upper bound on T in env.lenv.tpenv
   *
   * We only wish to do this if the types are in a possible subtype relationship.
   *)
  let (env, supertypes) =
    TUtils.get_concrete_supertypes ~abstract_enum:true env ivar_ty
  in
  let rec might_be_supertype ty =
    let (_env, ty) = Env.expand_type env ty in
    match get_node ty with
    | Tclass ((_, name), _, _)
      when String.equal name (Cls.name class_info)
           || Cls.has_ancestor class_info name
           || Cls.requires_ancestor class_info name ->
      true
    | Tdynamic -> true
    | Toption ty -> might_be_supertype ty
    | Tunion tyl -> List.for_all tyl ~f:might_be_supertype
    | _ -> false
  in
  let env =
    List.fold_left supertypes ~init:env ~f:(fun env ty ->
        if might_be_supertype ty then
          SubType.add_constraint
            env
            Ast_defs.Constraint_as
            obj_ty
            ty
            (Errors.unify_error_at p)
        else
          env)
  in
  (* It's often the case that the fresh name isn't necessary. For
   * example, if C<T> extends B<T>, and we have $x:B<t> for some type t
   * then $x is C should refine to $x:C<t>.
   * We take a simple approach:
   *    For a fresh type parameter T#1, if
   *      - There is an eqality constraint T#1 = t,
   *      - T#1 is covariant, and T#1 has upper bound t (or mixed if absent)
   *      - T#1 is contravariant, and T#1 has lower bound t (or nothing if absent)
   *    then replace T#1 with t.
   * This is done in Type_parameter_env_ops.simplify_tpenv
   *)
  let (env, tparam_substs) =
    Type_parameter_env_ops.simplify_tpenv
      env
      (List.zip_exn tparams_with_new_names tyl_fresh)
      reason
  in
  let tyl_fresh =
    List.map2_exn tyl_fresh tparams_with_new_names ~f:(fun orig_ty tparam_opt ->
        match tparam_opt with
        | None -> orig_ty
        | Some (_tp, name) -> SMap.find name tparam_substs)
  in
  let obj_ty_simplified =
    mk (get_reason obj_ty, Tclass (class_name, Nonexact, tyl_fresh))
  in
  (env, obj_ty_simplified)

(** Transform a hint like `A<_>` to a localized type like `A<T#1>` for refinement of
an instance variable. ivar_ty is the previous type of that instance variable. Return
the intersection of the hint and variable. *)
let rec class_for_refinement env p reason ivar_pos ivar_ty hint_ty =
  let (env, hint_ty) = Env.expand_type env hint_ty in
  match (get_node ivar_ty, get_node hint_ty) with
  | (_, Tclass (((_, cid) as _c), _, tyl)) ->
    begin
      match Env.get_class env cid with
      | Some class_info ->
        let (env, tparams_with_new_names, tyl_fresh) =
          generate_fresh_tparams env class_info p reason tyl
        in
        safely_refine_class_type
          env
          p
          _c
          class_info
          ivar_ty
          hint_ty
          reason
          tparams_with_new_names
          tyl_fresh
      | None -> (env, TUtils.terr env (Reason.Rwitness ivar_pos))
    end
  | (Ttuple ivar_tyl, Ttuple hint_tyl)
    when Int.equal (List.length ivar_tyl) (List.length hint_tyl) ->
    let (env, tyl) =
      List.map2_env env ivar_tyl hint_tyl ~f:(fun env ivar_ty hint_ty ->
          class_for_refinement env p reason ivar_pos ivar_ty hint_ty)
    in
    (env, MakeType.tuple reason tyl)
  | _ -> (env, hint_ty)

let refine_and_simplify_intersection env p reason ivar_pos ivar_ty hint_ty =
  match get_node ivar_ty with
  | Tunion [ty1; ty2]
    when Typing_defs.is_dynamic ty1 || Typing_defs.is_dynamic ty2 ->
    (* Distribute the intersection over the union *)
    let (env, hint_ty1) =
      class_for_refinement env p reason ivar_pos ty1 hint_ty
    in
    let (env, hint_ty2) =
      class_for_refinement env p reason ivar_pos ty2 hint_ty
    in
    let (env, ty1) = Inter.intersect env ~r:reason ty1 hint_ty1 in
    let (env, ty2) = Inter.intersect env ~r:reason ty2 hint_ty2 in
    Typing_union.union env ty1 ty2
  | _ ->
    let (env, hint_ty) =
      class_for_refinement env p reason ivar_pos ivar_ty hint_ty
    in
    Inter.intersect env ~r:reason ivar_ty hint_ty

type legacy_arrays =
  | PHPArray
  | AnyArray
  | HackDictOrDArray
  | HackVecOrVArray

(* Refine type for is_array, is_vec, is_keyset and is_dict tests
 * `pred_name` is the function name itself (e.g. 'is_vec')
 * `p` is position of the function name in the source
 * `arg_expr` is the argument to the function
 *)
let safely_refine_is_array env ty p pred_name arg_expr =
  refine_lvalue_type env arg_expr ~refine:(fun env arg_ty ->
      let r = Reason.Rpredicated (p, pred_name) in
      let (env, tarrkey_name) =
        Env.add_fresh_generic_parameter
          env
          (Pos_or_decl.of_raw_pos p)
          "Tk"
          ~reified:Erased
          ~enforceable:false
          ~newable:false
      in
      (* TODO(T69551141) handle type arguments for Tgeneric *)
      let tarrkey = MakeType.generic r tarrkey_name in
      let env =
        SubType.add_constraint
          env
          Ast_defs.Constraint_as
          tarrkey
          (MakeType.arraykey r)
          (Errors.unify_error_at p)
      in
      let (env, tfresh_name) =
        Env.add_fresh_generic_parameter
          env
          (Pos_or_decl.of_raw_pos p)
          "T"
          ~reified:Erased
          ~enforceable:false
          ~newable:false
      in
      (* TODO(T69551141) handle type arguments for Tgeneric *)
      let tfresh = MakeType.generic r tfresh_name in
      (* If we're refining the type for `is_array` we have a slightly more
       * involved process. Let's separate out that logic so we can re-use it.
       *)
      let array_ty =
        let tk = MakeType.arraykey Reason.(Rvarray_or_darray_key (to_pos r)) in
        let tv = tfresh in
        MakeType.varray_or_darray r tk tv
      in
      (* This is the refined type of e inside the branch *)
      let hint_ty =
        match ty with
        | PHPArray -> array_ty
        | AnyArray -> MakeType.any_array r tarrkey tfresh
        | HackDictOrDArray ->
          MakeType.union
            r
            [MakeType.dict r tarrkey tfresh; MakeType.darray r tarrkey tfresh]
        | HackVecOrVArray ->
          MakeType.union r [MakeType.vec r tfresh; MakeType.varray r tfresh]
      in
      let (_, arg_pos, _) = arg_expr in
      let (env, refined_ty) =
        class_for_refinement env p r arg_pos arg_ty hint_ty
      in
      (* Add constraints on generic parameters that must
       * hold for refined_ty <:arg_ty. For example, if arg_ty is Traversable<T>
       * and refined_ty is keyset<T#1> then we know T#1 <: T.
       * See analogous code in safely_refine_class_type.
       *)
      let (env, supertypes) =
        TUtils.get_concrete_supertypes ~abstract_enum:true env arg_ty
      in
      let env =
        List.fold_left supertypes ~init:env ~f:(fun env ty ->
            SubType.add_constraint
              env
              Ast_defs.Constraint_as
              hint_ty
              ty
              (Errors.unify_error_at p))
      in
      Inter.intersect ~r env refined_ty arg_ty)

let key_exists env pos shape field =
  let field = Tast.to_nast_expr field in
  refine_lvalue_type env shape ~refine:(fun env shape_ty ->
      match TUtils.shape_field_name env field with
      | None -> (env, shape_ty)
      | Some field_name ->
        let field_name = TShapeField.of_ast Pos_or_decl.of_raw_pos field_name in
        Typing_shapes.refine_shape field_name pos env shape_ty)

(** Add a fresh type parameter to [env] with a name starting [prefix]
    and a constraint on [ty]. *)
let synthesize_type_param env p prefix ty =
  let (env, name) = Env.fresh_param_name env prefix in
  let env = Env.add_upper_bound_global env name ty in
  let env = Env.add_lower_bound_global env name ty in

  let hint = (p, Aast.Habstr (name, [])) in
  (hint, env)

(** Transform calls to MyVisitor::makeTree with [f]. *)
let rec rewrite_expr_tree_maketree env expr f =
  let (pos, p, expr_) = expr in
  let (env, expr_) =
    match expr_ with
    | Call
        ( (fun_pos, p, (Lfun (fun_, idl) | Efun (fun_, idl))),
          targs,
          args,
          variadic ) ->
      (* Express tree literals containing splices use an anonymous
         function that returns the makeTree call.

          (function() {
             $0splice1 = "whatever";
             return MyVisitor::makeTree(...);
          })()
      *)
      let map_stmt env s =
        match s with
        | (pos, Return (Some expr)) ->
          let (env, expr) = rewrite_expr_tree_maketree env expr f in
          (env, (pos, Return (Some expr)))
        | _ -> (env, s)
      in

      let (env, body_ast) = List.map_env env fun_.f_body.fb_ast ~f:map_stmt in
      let fun_ = { fun_ with f_body = { fb_ast = body_ast } } in

      (env, Call ((fun_pos, p, Lfun (fun_, idl)), targs, args, variadic))
    | Call _ ->
      (* The desugarer might give us a simple call to makeTree, so we
         can process it immediately. *)
      f env expr_
    | _ -> (env, expr_)
  in
  (env, (pos, p, expr_))

(** Given [expr], a runtime expression for an expression tree, add a
    type parameter to the makeTree call.

    This enables expression tree visitors to use phantom type
    parameters. The visitor can be defined with __Explicit.

        public static function makeTree<<<__Explicit>> TInfer>(...) { ... }

     Userland calls to this method must provide an explicit type.

        MyVisitor::makeTree<MyVisitorInt>(...);

    For expression tree literals, we run type inference and provide a
    synthesized type parameter to the desugared runtime expression.

        MyVisitor`1`; // we infer MyVisitorInt
        // we add this constrained type parameter:
        MyVisitor::makeTree<TInfer#1>(...) where TInfer#1 = MyVisitorInt

 *)
let maketree_with_type_param env p expr expected_ty =
  let (hint_virtual, env) = synthesize_type_param env p "TInfer" expected_ty in
  let rewrite_expr env expr =
    match expr with
    | Call (e, _, el, unpacked_element) ->
      (env, Call (e, [((), hint_virtual)], el, unpacked_element))
    | e -> (env, e)
  in

  rewrite_expr_tree_maketree env expr rewrite_expr

module EnumClassLabelOps = struct
  type result =
    | Success of Tast.expr * locl_ty
    | ClassNotFound
    | LabelNotFound of Tast.expr * locl_ty
    | Invalid
    | Skip

  (** Given an [enum_name] and an [label], tries to see if
      [enum_name] has a constant named [label].
      In such case, creates the expected typed expression.

      If [label] is not there, it will register and error.

      [ctor] is either `MemberOf` or `Label`
      [full] describe if the original expression was a full
      label, as in E#A, or a short version, as in #A
  *)
  let expand pos env ~full ~ctor enum_name label_name =
    let cls = Env.get_class env enum_name in
    match cls with
    | Some cls ->
      (match Env.get_const env cls label_name with
      | Some const_def ->
        let dty = const_def.cc_type in
        (* the enum constant has type MemberOf<X, Y>. If we are
         * processing a Label argument, we just switch MemberOf for
         * Label.
         *)
        let dty =
          match deref dty with
          | (r, Tapply ((p, _), args)) -> mk (r, Tapply ((p, ctor), args))
          | _ -> dty
        in
        let (env, lty) = Phase.localize_no_subst env ~ignore_errors:true dty in
        let hi = lty in
        let qualifier =
          if full then
            Some (pos, enum_name)
          else
            None
        in
        let te = (hi, pos, EnumClassLabel (qualifier, label_name)) in
        (env, Success (te, lty))
      | None ->
        Errors.enum_class_label_unknown pos label_name enum_name;
        let r = Reason.Rwitness pos in
        let ty = Typing_utils.terr env r in
        let te = (ty, pos, EnumClassLabel (None, label_name)) in
        (env, LabelNotFound (te, ty)))
    | None -> (env, ClassNotFound)
end

let is_lvalue = function
  | `lvalue -> true
  | _ -> false

(* Given a localized parameter type and parameter information, infer
 * a type for the parameter default expression (if present) and check that
 * it is a subtype of the parameter type (if present). If no parameter type
 * is specified, then union with Tany. (So it's as though we did a conditional
 * assignment of the default expression to the parameter).
 * Set the type of the parameter in the locals environment *)
let rec bind_param
    env ?(immutable = false) ?(can_read_globals = false) (ty1, param) =
  let (env, param_te, ty1) =
    match param.param_expr with
    | None -> (env, None, ty1)
    | Some e ->
      let decl_hint =
        Option.map
          ~f:(Decl_hint.hint env.decl_env)
          (hint_of_type_hint param.param_type_hint)
      in
      let enforced =
        match decl_hint with
        | None -> Unenforced
        | Some ty -> Typing_enforceability.get_enforcement env ty
      in
      let ty1_enforced = { et_type = ty1; et_enforced = enforced } in
      let expected =
        ExpectedTy.make_and_allow_coercion_opt
          env
          param.param_pos
          Reason.URparam
          ty1_enforced
      in
      let (env, (te, ty2)) =
        let reason = Reason.Rwitness param.param_pos in
        let pure = MakeType.mixed reason in
        let cap =
          if can_read_globals then
            MakeType.capability reason SN.Capabilities.accessGlobals
          else
            pure
        in
        with_special_coeffects env cap pure @@ fun env ->
        expr ?expected env e ~allow_awaitable:(*?*) false |> triple_to_pair
      in
      Typing_sequencing.sequence_check_expr e;
      let (env, ty1) =
        if
          Option.is_none (hint_of_type_hint param.param_type_hint)
          && (not @@ TCO.global_inference (Env.get_tcopt env))
          (* ty1 will be Tany iff we have no type hint and we are not in
           * 'infer missing mode'. When it ty1 is Tany we just union it with
           * the type of the default expression *)
        then
          Union.union env ty1 ty2
        (* Otherwise we have an explicit type, and the default expression type
         * must be a subtype *)
        else
          let env =
            Typing_coercion.coerce_type
              param.param_pos
              Reason.URhint
              env
              ty2
              ty1_enforced
              Errors.parameter_default_value_wrong_type
          in
          (env, ty1)
      in
      (env, Some te, ty1)
  in
  let (env, user_attributes) =
    attributes_check_def
      env
      SN.AttributeKinds.parameter
      param.param_user_attributes
  in
  let tparam =
    {
      Aast.param_annotation = Tast.make_expr_annotation param.param_pos ty1;
      Aast.param_type_hint = (ty1, hint_of_type_hint param.param_type_hint);
      Aast.param_is_variadic = param.param_is_variadic;
      Aast.param_pos = param.param_pos;
      Aast.param_name = param.param_name;
      Aast.param_expr = param_te;
      Aast.param_callconv = param.param_callconv;
      Aast.param_readonly = param.param_readonly;
      Aast.param_user_attributes = user_attributes;
      Aast.param_visibility = param.param_visibility;
    }
  in
  let mode = get_param_mode param.param_callconv in
  let id = Local_id.make_unscoped param.param_name in
  let env = Env.set_local ~immutable env id ty1 param.param_pos in
  let env = Env.set_param env id (ty1, param.param_pos, mode) in
  let env =
    if has_accept_disposable_attribute param then
      Env.set_using_var env id
    else
      env
  in
  (env, tparam)

(*****************************************************************************)
(* function used to type closures, functions and methods *)
(*****************************************************************************)
and fun_ ?(abstract = false) ?(disable = false) env return pos named_body f_kind
    =
  Env.with_env env (fun env ->
      debug_last_pos := pos;
      let env = Env.set_return env return in
      let (env, tb) =
        if disable then
          let () =
            Errors.internal_error
              pos
              ("Type inference for this function has been disabled by the "
              ^ SN.UserAttributes.uaDisableTypecheckerInternal
              ^ " attribute")
          in
          block env []
        else
          block env named_body.fb_ast
      in
      Typing_sequencing.sequence_check_block named_body.fb_ast;
      let { Typing_env_return_info.return_type = ret; _ } =
        Env.get_return env
      in
      let has_implicit_return = LEnv.has_next env in
      let has_readonly = Env.get_readonly env in
      let env =
        if (not has_implicit_return) || abstract || Env.is_hhi env then
          env
        else
          Typing_return.fun_implicit_return env pos ret.et_type f_kind
      in
      let env =
        Typing_env.set_fun_tast_info
          env
          Tast.{ has_implicit_return; has_readonly }
      in
      debug_last_pos := Pos.none;
      (env, tb))

and block env stl =
  Typing_env.with_origin env Decl_counters.Body @@ fun env ->
  (* To insert an `AssertEnv`, `stmt` might return a `Block`. We eliminate it here
     to keep ASTs `Block`-free. *)
  let (env, stl) =
    List.fold ~init:(env, []) stl ~f:(fun (env, stl) st ->
        let (env, st) = stmt env st in
        (* Accumulate statements in reverse order *)
        let stl =
          match st with
          | (_, Aast.Block stl') -> List.rev stl' @ stl
          | _ -> st :: stl
        in
        (env, stl))
  in
  (env, List.rev stl)

(* Ensure that `ty` is a subtype of IDisposable (for `using`) or
 * IAsyncDisposable (for `await using`)
 *)
and has_dispose_method env has_await p e ty =
  let meth =
    if has_await then
      SN.Members.__disposeAsync
    else
      SN.Members.__dispose
  in
  let (_, obj_pos, _) = e in
  let (env, (tfty, _tal)) =
    TOG.obj_get
      ~obj_pos
      ~is_method:true
      ~inst_meth:false
      ~meth_caller:false
      ~nullsafe:None
      ~coerce_from_ty:None
      ~explicit_targs:[]
      ~class_id:(CIexpr e)
      ~member_id:(p, meth)
      ~on_error:(Errors.using_error p has_await)
      env
      ty
  in
  let (env, (_tel, _typed_unpack_element, _ty, _should_forget_fakes)) =
    call ~expected:None p env tfty [] None
  in
  env

(* Check an individual component in the expression `e` in the
 * `using (e) { ... }` statement.
 * This consists of either
 *   a simple assignment `$x = e`, in which `$x` is the using variable, or
 *   an arbitrary expression `e`, in which case a temporary is the using
 *      variable, inaccessible in the source.
 * Return the typed expression and its type, and any variables that must
 * be designated as "using variables" for avoiding escapes.
 *)
and check_using_expr has_await env ((_, pos, content) as using_clause) =
  match content with
  (* Simple assignment to local of form `$lvar = e` *)
  | Binop (Ast_defs.Eq None, (_, lvar_pos, Lvar lvar), e) ->
    let (env, te, ty) =
      expr ~is_using_clause:true env e ~allow_awaitable:(*?*) false
    in
    let env = has_dispose_method env has_await pos e ty in
    let env = set_local ~is_using_clause:true env lvar ty in
    (* We are assigning a new value to the local variable, so we need to
     * generate a new expression id
     *)
    let env = Env.set_local_expr_id env (snd lvar) (Ident.tmp ()) in
    ( env,
      ( Tast.make_typed_expr
          pos
          ty
          (Aast.Binop
             ( Ast_defs.Eq None,
               Tast.make_typed_expr lvar_pos ty (Aast.Lvar lvar),
               te )),
        [snd lvar] ) )
  (* Arbitrary expression. This will be assigned to a temporary *)
  | _ ->
    let (env, typed_using_clause, ty) =
      expr ~is_using_clause:true env using_clause ~allow_awaitable:(*?*) false
    in
    let env = has_dispose_method env has_await pos using_clause ty in
    (env, (typed_using_clause, []))

(* Check the using clause e in
 * `using (e) { ... }` statement (`has_await = false`) or
 * `await using (e) { ... }` statement (`has_await = true`).
 * `using_clauses` is a list of expressions.
 * Return the typed expression, and any variables that must
 * be designated as "using variables" for avoiding escapes.
 *)
and check_using_clause env has_await using_clauses =
  let (env, pairs) =
    List.map_env env using_clauses ~f:(check_using_expr has_await)
  in
  let (typed_using_clauses, vars) = List.unzip pairs in
  (env, typed_using_clauses, List.concat vars)

and stmt env (pos, st) =
  let (env, st) = stmt_ env pos st in
  Typing_debug.log_env_if_too_big pos env;
  (env, (pos, st))

and stmt_ env pos st =
  let expr ?(allow_awaitable = (*?*) false) = expr ~allow_awaitable in
  let exprs = exprs ~allow_awaitable:(*?*) false in
  (* Type check a loop. f env = (env, result) checks the body of the loop.
   * We iterate over the loop until the "next" continuation environment is
   * stable. alias_depth is supposed to be an upper bound on this; but in
   * certain cases this fails (e.g. a generic type grows unboundedly). TODO:
   * fix this.
   *)
  let infer_loop env f =
    let in_loop_outer = env.in_loop in
    let alias_depth =
      if in_loop_outer then
        1
      else
        Typing_alias.get_depth (pos, st)
    in
    let env = { env with in_loop = true } in
    let rec loop env n =
      (* Remember the old environment *)
      let old_next_entry = Env.next_cont_opt env in
      let (env, result) = f env in
      let new_next_entry = Env.next_cont_opt env in
      (* Finish if we reach the bound, or if the environments match *)
      if
        Int.equal n alias_depth
        || Typing_per_cont_ops.is_sub_opt_entry
             Typing_subtype.is_sub_type
             env
             new_next_entry
             old_next_entry
      then
        let env = { env with in_loop = in_loop_outer } in
        (env, result)
      else
        loop env (n + 1)
    in
    loop env 1
  in
  let env = Env.open_tyvars env pos in
  (fun (env, tb) -> (Typing_solver.close_tyvars_and_solve env, tb))
  @@
  match st with
  | Fallthrough ->
    let env = LEnv.move_and_merge_next_in_cont env C.Fallthrough in
    (env, Aast.Fallthrough)
  | Noop -> (env, Aast.Noop)
  | AssertEnv _ -> (env, Aast.Noop)
  | Yield_break ->
    let env = LEnv.move_and_merge_next_in_cont env C.Exit in
    (env, Aast.Yield_break)
  | Expr e ->
    let (env, te, _) = expr env e in
    let env =
      if TFTerm.typed_expression_exits te then
        LEnv.move_and_merge_next_in_cont env C.Exit
      else
        env
    in
    (env, Aast.Expr te)
  | If (e, b1, b2) ->
    let assert_refinement_env =
      assert_env_blk ~pos ~at:`Start Aast.Refinement
    in
    let (env, te, _) = expr env e in
    let (env, tb1, tb2) =
      branch
        env
        (fun env ->
          let (env, lset) = condition env true te in
          let refinement_map = refinement_annot_map env lset in
          let (env, b1) = block env b1 in
          let b1 = assert_refinement_env refinement_map b1 in
          (env, b1))
        (fun env ->
          let (env, lset) = condition env false te in
          let refinement_map = refinement_annot_map env lset in
          let (env, b2) = block env b2 in
          let b2 = assert_refinement_env refinement_map b2 in
          (env, b2))
    in
    (* TODO TAST: annotate with joined types *)
    (env, Aast.If (te, tb1, tb2))
  | Return None ->
    let env = Typing_return.check_inout_return pos env in
    let rty = MakeType.void (Reason.Rwitness pos) in
    let { Typing_env_return_info.return_type = expected_return; _ } =
      Env.get_return env
    in
    let expected_return =
      Typing_return.strip_awaitable (Env.get_fn_kind env) env expected_return
    in
    let env =
      match Env.get_fn_kind env with
      | Ast_defs.FGenerator
      | Ast_defs.FAsyncGenerator ->
        env
      | _ ->
        Typing_return.implicit_return
          env
          pos
          ~expected:expected_return.et_type
          ~actual:rty
    in
    let env = LEnv.move_and_merge_next_in_cont env C.Exit in
    (env, Aast.Return None)
  | Return (Some e) ->
    let env = Typing_return.check_inout_return pos env in
    let (_, expr_pos, _) = e in
    let Typing_env_return_info.
          {
            return_type;
            return_disposable;
            return_explicit;
            return_dynamically_callable = _;
          } =
      Env.get_return env
    in
    let return_type =
      Typing_return.strip_awaitable (Env.get_fn_kind env) env return_type
    in
    let expected =
      if return_explicit then
        Some
          (ExpectedTy.make_and_allow_coercion
             expr_pos
             Reason.URreturn
             return_type)
      else
        None
    in
    if return_disposable then enforce_return_disposable env e;
    let (env, te, rty) =
      expr ~is_using_clause:return_disposable ?expected env e
    in
    (* This is a unify_error rather than a return_type_mismatch because the return
     * statement is the problem, not the return type itself. *)
    let (env, err_opt) =
      Result.fold
        ~ok:(fun env -> (env, None))
        ~error:(fun env -> (env, Some (rty, return_type.et_type)))
      @@ Typing_coercion.coerce_type_res
           expr_pos
           Reason.URreturn
           env
           rty
           return_type
           Errors.unify_error
    in
    let env = LEnv.move_and_merge_next_in_cont env C.Exit in
    (env, Aast.Return (Some (hole_on_err ~err_opt te)))
  | Do (b, e) ->
    (* NOTE: leaks scope as currently implemented; this matches
       the behavior in naming (cf. `do_stmt` in naming/naming.ml).
    *)
    let (env, (tb, te)) =
      LEnv.stash_and_do env [C.Continue; C.Break; C.Do] (fun env ->
          let env = LEnv.save_and_merge_next_in_cont env C.Do in
          let (env, _) = block env b in
          (* saving the locals in continue here even if there is no continue
           * statement because they must be merged at the end of the loop, in
           * case there is no iteration *)
          let env = LEnv.save_and_merge_next_in_cont env C.Continue in
          let (env, tb) =
            infer_loop env (fun env ->
                let env =
                  LEnv.update_next_from_conts env [C.Continue; C.Next]
                in
                (* The following is necessary in case there is an assignment in the
                 * expression *)
                let (env, te, _) = expr env e in
                let (env, _lset) = condition env true te in
                let env = LEnv.update_next_from_conts env [C.Do; C.Next] in
                let (env, tb) = block env b in
                (env, tb))
          in
          let env = LEnv.update_next_from_conts env [C.Continue; C.Next] in
          let (env, te, _) = expr env e in
          let (env, _lset) = condition env false te in
          let env = LEnv.update_next_from_conts env [C.Break; C.Next] in
          (env, (tb, te)))
    in
    (env, Aast.Do (tb, te))
  | While (e, b) ->
    let (env, (te, tb, refinement_map)) =
      LEnv.stash_and_do env [C.Continue; C.Break] (fun env ->
          let env = LEnv.save_and_merge_next_in_cont env C.Continue in
          let (env, tb) =
            infer_loop env (fun env ->
                let env =
                  LEnv.update_next_from_conts env [C.Continue; C.Next]
                in
                let join_map = annot_map env in
                (* The following is necessary in case there is an assignment in the
                 * expression *)
                let (env, te, _) = expr env e in
                let (env, lset) = condition env true te in
                let refinement_map = refinement_annot_map env lset in
                (* TODO TAST: avoid repeated generation of block *)
                let (env, tb) = block env b in

                (* Annotate loop body with join and refined environments *)
                let assert_env_blk = assert_env_blk ~pos ~at:`Start in
                let tb = assert_env_blk Aast.Refinement refinement_map tb in
                let tb = assert_env_blk Aast.Join join_map tb in

                (env, tb))
          in
          let env = LEnv.update_next_from_conts env [C.Continue; C.Next] in
          let (env, te, _) = expr env e in
          let (env, lset) = condition env false te in
          let refinement_map_at_exit = refinement_annot_map env lset in
          let env = LEnv.update_next_from_conts env [C.Break; C.Next] in
          (env, (te, tb, refinement_map_at_exit)))
    in
    let while_st = Aast.While (te, tb) in
    (* Export the refined environment after the exit condition holds *)
    let while_st =
      assert_env_stmt ~pos ~at:`End Aast.Refinement refinement_map while_st
    in
    (env, while_st)
  | Using
      {
        us_has_await = has_await;
        us_exprs = (loc, using_clause);
        us_block = using_block;
        us_is_block_scoped;
      } ->
    let (env, typed_using_clause, using_vars) =
      check_using_clause env has_await using_clause
    in
    let (env, typed_using_block) = block env using_block in
    (* Remove any using variables from the environment, as they should not
     * be in scope outside the block *)
    let env = List.fold_left using_vars ~init:env ~f:Env.unset_local in
    ( env,
      Aast.Using
        Aast.
          {
            us_has_await = has_await;
            us_exprs = (loc, typed_using_clause);
            us_block = typed_using_block;
            us_is_block_scoped;
          } )
  | For (e1, e2, e3, b) ->
    let e2 =
      match e2 with
      | Some e2 -> e2
      | None -> ((), Pos.none, True)
    in
    let (env, (te1, te2, te3, tb, refinement_map)) =
      LEnv.stash_and_do env [C.Continue; C.Break] (fun env ->
          (* For loops leak their initalizer, but nothing that's defined in the
             body
          *)
          let (env, te1, _) = exprs env e1 in
          (* initializer *)
          let env = LEnv.save_and_merge_next_in_cont env C.Continue in
          let (env, (tb, te3)) =
            infer_loop env (fun env ->
                (* The following is necessary in case there is an assignment in the
                 * expression *)
                let (env, te2, _) = expr env e2 in
                let (env, lset) = condition env true te2 in
                let refinement_map = refinement_annot_map env lset in
                let (env, tb) = block env b in
                let env =
                  LEnv.update_next_from_conts env [C.Continue; C.Next]
                in
                let join_map = annot_map env in
                let (env, te3, _) = exprs env e3 in

                (* Export the join and refinement environments *)
                let assert_env_blk = assert_env_blk ~pos ~at:`Start in
                let tb = assert_env_blk Aast.Refinement refinement_map tb in
                let tb = assert_env_blk Aast.Join join_map tb in

                (env, (tb, te3)))
          in
          let env = LEnv.update_next_from_conts env [C.Continue; C.Next] in
          let (env, te2, _) = expr env e2 in
          let (env, lset) = condition env false te2 in
          let refinement_map_at_exit = refinement_annot_map env lset in
          let env = LEnv.update_next_from_conts env [C.Break; C.Next] in
          (env, (te1, te2, te3, tb, refinement_map_at_exit)))
    in
    let for_st = Aast.For (te1, Some te2, te3, tb) in
    let for_st =
      assert_env_stmt ~pos ~at:`End Aast.Refinement refinement_map for_st
    in
    (env, for_st)
  | Switch (((_, pos, _) as e), cl) ->
    let (env, te, ty) = expr env e in
    (* NB: A 'continue' inside a 'switch' block is equivalent to a 'break'.
     * See the note in
     * http://php.net/manual/en/control-structures.continue.php *)
    let (env, (te, tcl)) =
      LEnv.stash_and_do env [C.Continue; C.Break] (fun env ->
          let parent_locals = LEnv.get_all_locals env in
          let (env, tcl) = case_list parent_locals ty env pos cl in
          let env =
            LEnv.update_next_from_conts env [C.Continue; C.Break; C.Next]
          in
          (env, (te, tcl)))
    in
    (env, Aast.Switch (te, tcl))
  | Foreach (e1, e2, b) ->
    (* It's safe to do foreach over a disposable, as no leaking is possible *)
    let (env, te1, ty1) = expr ~accept_using_var:true env e1 in
    let (env, (te1, te2, tb)) =
      LEnv.stash_and_do env [C.Continue; C.Break] (fun env ->
          let env = LEnv.save_and_merge_next_in_cont env C.Continue in
          let (_, p1, _) = e1 in
          let (env, tk, tv, err_opt) = as_expr env ty1 p1 e2 in
          let (env, (te2, tb)) =
            infer_loop env (fun env ->
                let env =
                  LEnv.update_next_from_conts env [C.Continue; C.Next]
                in
                let join_map = annot_map env in
                let (env, te2) = bind_as_expr env p1 tk tv e2 in
                let (env, tb) = block env b in
                (* Export the join environment *)
                let tb = assert_env_blk ~pos ~at:`Start Aast.Join join_map tb in
                (env, (te2, tb)))
          in
          let env =
            LEnv.update_next_from_conts env [C.Continue; C.Break; C.Next]
          in
          (env, (hole_on_err ~err_opt te1, te2, tb)))
    in
    (env, Aast.Foreach (te1, te2, tb))
  | Try (tb, cl, fb) ->
    let (env, ttb, tcl, tfb) = try_catch env tb cl fb in
    (env, Aast.Try (ttb, tcl, tfb))
  | Awaitall (el, b) ->
    let env = might_throw env in
    let (env, el) =
      List.fold_left el ~init:(env, []) ~f:(fun (env, tel) (e1, e2) ->
          let (env, te2, ty2) = expr env e2 ~allow_awaitable:true in
          let (_, pos2, _) = e2 in
          let (env, ty2) =
            Async.overload_extract_from_awaitable env ~p:pos2 ty2
          in
          match e1 with
          | Some e1 ->
            let pos = fst e1 in
            let (env, _, _, err_opt) =
              assign pos env ((), pos, Lvar e1) pos2 ty2
            in
            (env, (Some e1, hole_on_err ~err_opt te2) :: tel)
          | None -> (env, (None, te2) :: tel))
    in
    let (env, b) = block env b in
    (env, Aast.Awaitall (el, b))
  | Throw e ->
    let (_, p, _) = e in
    let (env, te, ty) = expr env e in
    let env = coerce_to_throwable p env ty in
    let env = move_and_merge_next_in_catch env in
    (env, Aast.Throw te)
  | Continue ->
    let env = LEnv.move_and_merge_next_in_cont env C.Continue in
    (env, Aast.Continue)
  | Break ->
    let env = LEnv.move_and_merge_next_in_cont env C.Break in
    (env, Aast.Break)
  | Block _
  | Markup _ ->
    failwith
      "Unexpected nodes in AST. These nodes should have been removed in naming."

and finally_cont fb env ctx =
  (* The only locals in scope are the ones from the current continuation *)
  let env = Env.env_with_locals env @@ CMap.singleton C.Next ctx in
  let (env, _tfb) = block env fb in
  (env, LEnv.get_all_locals env)

and finally env fb =
  match fb with
  | [] ->
    let env = LEnv.update_next_from_conts env [C.Next; C.Finally] in
    (env, [])
  | _ ->
    let parent_locals = LEnv.get_all_locals env in
    (* First typecheck the finally block against all continuations merged
       * together.
       * During this phase, record errors found in the finally block, but discard
       * the resulting environment. *)
    let all_conts = Env.all_continuations env in
    let env = LEnv.update_next_from_conts env all_conts in
    let (env, tfb) = block env fb in
    let env = LEnv.restore_conts_from env parent_locals all_conts in
    (* Second, typecheck the finally block once against each continuation. This
       * helps be more clever about what each continuation will be after the
       * finally block.
       * We don't want to record errors during this phase, because certain types
       * of errors will fire wrongly. For example, if $x is nullable in some
       * continuations but not in others, then we must use `?->` on $x, but an
       * error will fire when typechecking the finally block againts continuations
       * where $x is non-null. *)
    let finally_cont env _key = finally_cont fb env in
    let (env, locals_map) =
      Errors.ignore_ (fun () -> CMap.map_env finally_cont env parent_locals)
    in
    let union env _key = LEnv.union_contextopts env in
    let (env, locals) = Try.finally_merge union env locals_map all_conts in
    (Env.env_with_locals env locals, tfb)

and try_catch env tb cl fb =
  let parent_locals = LEnv.get_all_locals env in
  let env =
    LEnv.drop_conts env [C.Break; C.Continue; C.Exit; C.Catch; C.Finally]
  in
  let (env, (ttb, tcb)) =
    Env.in_try env (fun env ->
        let (env, ttb) = block env tb in
        let env = LEnv.move_and_merge_next_in_cont env C.Finally in
        let catchctx = LEnv.get_cont_option env C.Catch in
        let (env, lenvtcblist) = List.map_env env ~f:(catch catchctx) cl in
        let (lenvl, tcb) = List.unzip lenvtcblist in
        let env = LEnv.union_lenv_list env env.lenv lenvl in
        let env = LEnv.move_and_merge_next_in_cont env C.Finally in
        (env, (ttb, tcb)))
  in
  let (env, tfb) = finally env fb in
  let env = LEnv.update_next_from_conts env [C.Finally] in
  let env = LEnv.drop_cont env C.Finally in
  let env =
    LEnv.restore_and_merge_conts_from
      env
      parent_locals
      [C.Break; C.Continue; C.Exit; C.Catch; C.Finally]
  in
  (env, ttb, tcb, tfb)

and case_list parent_locals ty env switch_pos cl =
  let initialize_next_cont env =
    let env = LEnv.restore_conts_from env parent_locals [C.Next] in
    let env = LEnv.update_next_from_conts env [C.Next; C.Fallthrough] in
    LEnv.drop_cont env C.Fallthrough
  in
  let check_fallthrough env switch_pos case_pos block rest_of_list ~is_default =
    if (not (List.is_empty block)) && not (List.is_empty rest_of_list) then
      match LEnv.get_cont_option env C.Next with
      | Some _ ->
        if is_default then
          Errors.default_fallthrough switch_pos
        else
          Errors.case_fallthrough switch_pos case_pos
      | None -> ()
  in
  let env =
    (* below, we try to find out if the switch is exhaustive *)
    let has_default =
      List.exists cl ~f:(function
          | Default _ -> true
          | _ -> false)
    in
    let (env, ty) =
      (* If it hasn't got a default clause then we need to solve type variables
       * in order to check for an enum *)
      if has_default then
        Env.expand_type env ty
      else
        Typing_solver.expand_type_and_solve
          env
          ~description_of_expected:"a value"
          switch_pos
          ty
    in
    (* leverage that enums are checked for exhaustivity *)
    let is_enum =
      let top_type =
        MakeType.class_type
          Reason.Rnone
          SN.Classes.cHH_BuiltinEnum
          [MakeType.mixed Reason.Rnone]
      in
      Typing_subtype.is_sub_type_for_coercion env ty top_type
    in
    (* register that the runtime may throw in case we cannot prove
       that the switch is exhaustive *)
    if has_default || is_enum then
      env
    else
      might_throw env
  in
  let rec case_list env = function
    | [] -> (env, [])
    | Default (pos, b) :: rl ->
      let env = initialize_next_cont env in
      let (env, tb) = block env b in
      check_fallthrough env switch_pos pos b rl ~is_default:true;
      let (env, tcl) = case_list env rl in
      (env, Aast.Default (pos, tb) :: tcl)
    | Case (((_, pos, _) as e), b) :: rl ->
      let env = initialize_next_cont env in
      let (env, te, _) = expr env e ~allow_awaitable:(*?*) false in
      let (env, tb) = block env b in
      check_fallthrough env switch_pos pos b rl ~is_default:false;
      let (env, tcl) = case_list env rl in
      (env, Aast.Case (te, tb) :: tcl)
  in
  case_list env cl

and catch catchctx env (sid, exn_lvar, b) =
  let env = LEnv.replace_cont env C.Next catchctx in
  let cid = CI sid in
  let ety_p = fst sid in
  let (env, _, _, _) = instantiable_cid ety_p env cid [] in
  let (env, _tal, _te, ety) = class_expr env [] ((), ety_p, cid) in
  let env = coerce_to_throwable ety_p env ety in
  let (p, x) = exn_lvar in
  let env = set_valid_rvalue p env x ety in
  let (env, tb) = block env b in
  (env, (env.lenv, (sid, exn_lvar, tb)))

and bind_as_expr env p ty1 ty2 aexpr =
  match aexpr with
  | As_v ev ->
    let (env, te, _, _) = assign p env ev p ty2 in
    (env, Aast.As_v te)
  | Await_as_v (p, ev) ->
    let (env, te, _, _) = assign p env ev p ty2 in
    (env, Aast.Await_as_v (p, te))
  | As_kv ((_, p, Lvar ((_, k) as id)), ev) ->
    let env = set_valid_rvalue p env k ty1 in
    let (env, te, _, _) = assign p env ev p ty2 in
    let tk = Tast.make_typed_expr p ty1 (Aast.Lvar id) in
    (env, Aast.As_kv (tk, te))
  | Await_as_kv (p, (_, p1, Lvar ((_, k) as id)), ev) ->
    let env = set_valid_rvalue p env k ty1 in
    let (env, te, _, _) = assign p env ev p ty2 in
    let tk = Tast.make_typed_expr p1 ty1 (Aast.Lvar id) in
    (env, Aast.Await_as_kv (p, tk, te))
  | _ ->
    (* TODO Probably impossible, should check that *)
    assert false

and expr
    ?(expected : ExpectedTy.t option)
    ?(accept_using_var = false)
    ?(is_using_clause = false)
    ?(in_readonly_expr = false)
    ?(valkind = `other)
    ?(check_defined = true)
    ?in_await
    ~allow_awaitable
    env
    ((_, p, _) as e) =
  try
    begin
      match expected with
      | None -> ()
      | Some ExpectedTy.{ reason = r; ty = { et_type = ty; _ }; _ } ->
        Typing_log.(
          log_with_level env "typing" ~level:1 (fun () ->
              log_types
                (Pos_or_decl.of_raw_pos p)
                env
                [
                  Log_head
                    ( "Typing.expr " ^ Typing_reason.string_of_ureason r,
                      [Log_type ("expected_ty", ty)] );
                ]))
    end;
    raw_expr
      ~accept_using_var
      ~is_using_clause
      ~in_readonly_expr
      ~valkind
      ~check_defined
      ?in_await
      ?expected
      ~allow_awaitable
      env
      e
  with
  | Inf.InconsistentTypeVarState _ as e ->
    (* we don't want to catch unwanted exceptions here, eg Timeouts *)
    Errors.exception_occurred p (Exception.wrap e);
    make_result env p (invalid_expr_ env p) @@ err_witness env p

(* Some (legacy) special functions are allowed in initializers,
  therefore treat them as pure and insert the matching capabilities. *)
and expr_with_pure_coeffects
    ?(expected : ExpectedTy.t option) ~allow_awaitable env e =
  let (_, p, _) = e in
  let pure = MakeType.mixed (Reason.Rwitness p) in
  let (env, (te, ty)) =
    with_special_coeffects env pure pure @@ fun env ->
    expr env e ?expected ~allow_awaitable |> triple_to_pair
  in
  (env, te, ty)

and raw_expr
    ?(accept_using_var = false)
    ?(is_using_clause = false)
    ?(in_readonly_expr = false)
    ?(expected : ExpectedTy.t option)
    ?lhs_of_null_coalesce
    ?(valkind = `other)
    ?(check_defined = true)
    ?in_await
    ~allow_awaitable
    env
    e =
  let (_, p, _) = e in
  debug_last_pos := p;
  expr_
    ~accept_using_var
    ~is_using_clause
    ~in_readonly_expr
    ?expected
    ?lhs_of_null_coalesce
    ?in_await
    ~allow_awaitable
    ~valkind
    ~check_defined
    env
    e

and lvalue env e =
  let valkind = `lvalue in
  expr_ ~valkind ~check_defined:false env e ~allow_awaitable:(*?*) false

and lvalues env el =
  match el with
  | [] -> (env, [], [])
  | e :: el ->
    let (env, te, ty) = lvalue env e in
    let (env, tel, tyl) = lvalues env el in
    (env, te :: tel, ty :: tyl)

(* $x ?? 0 is handled similarly to $x ?: 0, except that the latter will also
 * look for sketchy null checks in the condition. *)
(* TODO TAST: type refinement should be made explicit in the typed AST *)
and eif env ~(expected : ExpectedTy.t option) ?in_await p c e1 e2 =
  let condition = condition ~lhs_of_null_coalesce:false in
  let (env, tc, tyc) =
    raw_expr ~lhs_of_null_coalesce:false env c ~allow_awaitable:false
  in
  let parent_lenv = env.lenv in
  let (env, _lset) = condition env true tc in
  let (env, te1, ty1) =
    match e1 with
    | None ->
      let (env, ty) =
        Typing_solver.non_null env (Pos_or_decl.of_raw_pos p) tyc
      in
      (env, None, ty)
    | Some e1 ->
      let (env, te1, ty1) =
        expr ?expected ?in_await env e1 ~allow_awaitable:true
      in
      (env, Some te1, ty1)
  in
  let lenv1 = env.lenv in
  let env = { env with lenv = parent_lenv } in
  let (env, _lset) = condition env false tc in
  let (env, te2, ty2) = expr ?expected ?in_await env e2 ~allow_awaitable:true in
  let lenv2 = env.lenv in
  let env = LEnv.union_lenvs env parent_lenv lenv1 lenv2 in
  let (env, ty) = Union.union ~approx_cancel_neg:true env ty1 ty2 in
  make_result env p (Aast.Eif (tc, te1, te2)) ty

and exprs
    ?(accept_using_var = false)
    ?(expected : ExpectedTy.t option)
    ?(valkind = `other)
    ?(check_defined = true)
    ~allow_awaitable
    env
    el =
  match el with
  | [] -> (env, [], [])
  | e :: el ->
    let (env, te, ty) =
      expr
        ~accept_using_var
        ?expected
        ~valkind
        ~check_defined
        env
        e
        ~allow_awaitable
    in
    let (env, tel, tyl) =
      exprs
        ~accept_using_var
        ?expected
        ~valkind
        ~check_defined
        env
        el
        ~allow_awaitable
    in
    (env, te :: tel, ty :: tyl)

and argument_list_exprs expr_cb env el =
  match el with
  | [] -> (env, [], [])
  | (pk, e) :: el ->
    let (env, te, ty) = expr_cb env e in
    let (env, tel, tyl) = argument_list_exprs expr_cb env el in
    (env, (pk, te) :: tel, ty :: tyl)

and exprs_expected (pos, ur, expected_tyl) env el =
  match (el, expected_tyl) with
  | ([], _) -> (env, [], [])
  | (e :: el, expected_ty :: expected_tyl) ->
    let expected = ExpectedTy.make pos ur expected_ty in
    let (env, te, ty) = expr ~expected env e ~allow_awaitable:(*?*) false in
    let (env, tel, tyl) = exprs_expected (pos, ur, expected_tyl) env el in
    (env, te :: tel, ty :: tyl)
  | (el, []) -> exprs env el ~allow_awaitable:(*?*) false

and expr_
    ?(expected : ExpectedTy.t option)
    ?(accept_using_var = false)
    ?(is_using_clause = false)
    ?(in_readonly_expr = false)
    ?lhs_of_null_coalesce
    ?in_await
    ~allow_awaitable
    ~(valkind : [> `lvalue | `lvalue_subexpr | `other ])
    ~check_defined
    env
    ((_, p, e) as outer) =
  let env = Env.open_tyvars env p in
  (fun (env, te, ty) ->
    let env = Typing_solver.close_tyvars_and_solve env in
    (env, te, ty))
  @@
  let expr ?(allow_awaitable = allow_awaitable) =
    expr ~check_defined ~allow_awaitable
  in
  let exprs = exprs ~check_defined ~allow_awaitable in
  let raw_expr ?(allow_awaitable = allow_awaitable) =
    raw_expr ~check_defined ~allow_awaitable
  in
  (*
   * Given a list of types, computes their supertype. If any of the types are
   * unknown (e.g., comes from PHP), the supertype will be Typing_utils.tany env.
   * The optional coerce_for_op parameter controls whether any arguments of type
   * dynamic can be coerced to enforceable types because they are arguments to a
   * built-in operator.
   *)
  let compute_supertype
      ~(expected : ExpectedTy.t option)
      ~reason
      ~use_pos
      ?bound
      ?(coerce_for_op = false)
      ?(can_pessimise = false)
      r
      env
      tys =
    let (env, supertype) =
      match expected with
      | None ->
        let (env, supertype) = Env.fresh_type_reason env use_pos r in
        let env =
          match bound with
          | None -> env
          | Some ty ->
            (* There can't be an error because the type is fresh *)
            SubType.sub_type env supertype ty (fun ?code:_ ?quickfixes:_ _ ->
                Errors.internal_error use_pos "Subtype of fresh type variable")
        in
        (env, supertype)
      | Some ExpectedTy.{ ty = { et_type = ty; _ }; _ } -> (env, ty)
    in
    match get_node supertype with
    (* No need to check individual subtypes if expected type is mixed or any! *)
    | Tany _ -> (env, supertype, List.map tys ~f:(fun _ -> None))
    | _ ->
      let (env, expected_supertype) =
        if coerce_for_op then
          ( env,
            Some
              (ExpectedTy.make_and_allow_coercion
                 use_pos
                 reason
                 { et_type = supertype; et_enforced = Enforced }) )
        else
          let (env, pess_supertype) =
            if can_pessimise then
              Typing_array_access.maybe_pessimise_type env supertype
            else
              (env, supertype)
          in
          (env, Some (ExpectedTy.make use_pos reason pess_supertype))
      in
      let dyn_t = MakeType.dynamic Reason.Rnone in
      let subtype_value env ty =
        let (env, ty) =
          if coerce_for_op && Typing_utils.is_sub_type_for_union env dyn_t ty
          then
            (* if we're coercing for a primop, and we're going to use the coercion
               (because the type of the arg is a supertype of dynamic), then we want
               to force the expected_supertype to the bound.
            *)
            match bound with
            | None -> (env, ty)
            | Some bound_ty ->
              Typing_union.union env ty (mk (get_reason ty, get_node bound_ty))
          else
            (env, ty)
        in
        check_expected_ty_res
          ~coerce_for_op
          "Collection"
          env
          ty
          expected_supertype
      in
      let (env, rev_ty_err_opts) =
        List.fold_left tys ~init:(env, []) ~f:(fun (env, errs) ty ->
            Result.fold
              ~ok:(fun env -> (env, None :: errs))
              ~error:(fun env -> (env, Some (ty, supertype) :: errs))
            @@ subtype_value env ty)
      in

      if
        List.exists tys ~f:(fun ty ->
            equal_locl_ty_ (get_node ty) (Typing_utils.tany env))
      then
        (* If one of the values comes from PHP land, we have to be conservative
         * and consider that we don't know what the type of the values are. *)
        (env, Typing_utils.mk_tany env p, List.rev rev_ty_err_opts)
      else
        (env, supertype, List.rev rev_ty_err_opts)
  in
  (*
   * Given a 'a list and a method to extract an expr and its ty from a 'a, this
   * function extracts a list of exprs from the list, and computes the supertype
   * of all of the expressions' tys.
   *)
  let compute_exprs_and_supertype
      ~(expected : ExpectedTy.t option)
      ?(reason = Reason.URarray_value)
      ?(can_pessimise = false)
      ~bound
      ~coerce_for_op
      ~use_pos
      r
      env
      l
      extract_expr_and_ty =
    let (env, pess_expected) =
      if can_pessimise then
        match expected with
        | None -> (env, None)
        | Some ety ->
          let (env, ty) =
            Typing_array_access.maybe_pessimise_type
              env
              ety.ExpectedTy.ty.et_type
          in
          (env, Some ExpectedTy.(make ety.pos ety.reason ty))
      else
        (env, expected)
    in
    let (env, exprs_and_tys) =
      List.map_env env l ~f:(extract_expr_and_ty ~expected:pess_expected)
    in
    let (exprs, tys) = List.unzip exprs_and_tys in
    let (env, supertype, err_opts) =
      compute_supertype
        ~expected
        ~reason
        ~use_pos
        ?bound
        ~coerce_for_op
        ~can_pessimise
        r
        env
        tys
    in
    ( env,
      List.map2_exn
        ~f:(fun te err_opt -> hole_on_err te ~err_opt)
        exprs
        err_opts,
      supertype )
  in
  let check_collection_tparams env name tys =
    (* varrays and darrays are not classes but they share the same
       constraints with vec and dict respectively *)
    let name =
      if String.equal name SN.Typehints.varray then
        SN.Collections.cVec
      else if String.equal name SN.Typehints.darray then
        SN.Collections.cDict
      else
        name
    in
    (* Class retrieval always succeeds because we're fetching a
       collection decl from an HHI file. *)
    match Env.get_class env name with
    | Some class_ ->
      let ety_env =
        {
          (empty_expand_env_with_on_error
             (Env.invalid_type_hint_assert_primary_pos_in_current_decl env))
          with
          substs = TUtils.make_locl_subst_for_class_tparams class_ tys;
        }
      in
      Phase.check_tparams_constraints
        ~use_pos:p
        ~ety_env
        env
        (Cls.tparams class_)
    | None ->
      let desc = "Missing collection decl during type parameter check" in
      Telemetry.(create () |> string_ ~key:"class name" ~value:name)
      |> Errors.invariant_violation p ~desc ~report_to_user:false;
      (* Continue typechecking without performing the check on a best effort
         basis. *)
      env
  in
  Typing_type_wellformedness.expr env outer;
  match e with
  | Import _
  | Collection _ ->
    failwith "AST should not contain these nodes"
  | Hole (e, _, _, _) ->
    expr_
      ?expected
      ~accept_using_var
      ~is_using_clause
      ?lhs_of_null_coalesce
      ?in_await
      ~allow_awaitable
      ~valkind
      ~check_defined
      env
      e
  | Omitted ->
    let ty = Typing_utils.mk_tany env p in
    make_result env p Aast.Omitted ty
  | Varray (th, el)
  | ValCollection (_, th, el) ->
    let (get_expected_kind, name, subtype_val, coerce_for_op, make_expr, make_ty)
        =
      match e with
      | ValCollection (kind, _, _) ->
        let class_name = Nast.vc_kind_to_name kind in
        let (subtype_val, coerce_for_op) =
          match kind with
          | Set
          | ImmSet
          | Keyset ->
            (arraykey_value ~add_hole:true p class_name true, true)
          | Vector
          | ImmVector
          | Vec ->
            (array_value, false)
        in
        ( get_vc_inst env kind,
          class_name,
          subtype_val,
          coerce_for_op,
          (fun th elements -> Aast.ValCollection (kind, th, elements)),
          fun value_ty ->
            MakeType.class_type (Reason.Rwitness p) class_name [value_ty] )
      | Varray _ ->
        ( get_vc_inst env Vec,
          "varray",
          array_value,
          false,
          (fun th elements -> Aast.ValCollection (Vec, th, elements)),
          (fun value_ty -> MakeType.varray (Reason.Rwitness p) value_ty) )
      | _ ->
        (* The parent match makes this case impossible *)
        failwith "impossible match case"
    in
    (* Use expected type to determine expected element type *)
    let (env, elem_expected, th) =
      match th with
      | Some (_, tv) ->
        let (env, tv, tv_expected) = localize_targ env tv in
        let env = check_collection_tparams env name [fst tv] in
        (env, Some tv_expected, Some tv)
      | _ ->
        begin
          match expand_expected_and_get_node env expected with
          | (env, Some (pos, ur, ety, _)) ->
            begin
              match get_expected_kind ety with
              | Some vty -> (env, Some (ExpectedTy.make pos ur vty), None)
              | None -> (env, None, None)
            end
          | _ -> (env, None, None)
        end
    in
    let bound =
      (* TODO We ought to apply the bound even when not in sound dynamic mode,
         to avoid getting Set<dynamic> etc which are unsafe "nothing" factories. *)
      if coerce_for_op && TypecheckerOptions.enable_sound_dynamic env.genv.tcopt
      then
        Some
          (MakeType.arraykey
             (Reason.Rtype_variable_generics (p, "Tk", strip_ns name)))
      else
        None
    in
    let (env, tel, elem_ty) =
      compute_exprs_and_supertype
        ~expected:elem_expected
        ~use_pos:p
        ~reason:Reason.URvector
        ~can_pessimise:true
        ~coerce_for_op
        ~bound
        (Reason.Rtype_variable_generics (p, "T", strip_ns name))
        env
        el
        subtype_val
    in
    make_result env p (make_expr th tel) (make_ty elem_ty)
  | Darray (th, l)
  | KeyValCollection (_, th, l) ->
    let (get_expected_kind, name, make_expr, make_ty) =
      match e with
      | KeyValCollection (kind, _, _) ->
        let class_name = Nast.kvc_kind_to_name kind in
        ( get_kvc_inst env p kind,
          class_name,
          (fun th pairs -> Aast.KeyValCollection (kind, th, pairs)),
          (fun k v -> MakeType.class_type (Reason.Rwitness p) class_name [k; v])
        )
      | Darray _ ->
        let name = "darray" in
        ( get_kvc_inst env p Dict,
          name,
          (fun th pairs -> Aast.KeyValCollection (Dict, th, pairs)),
          (fun k v -> MakeType.darray (Reason.Rwitness p) k v) )
      | _ ->
        (* The parent match makes this case impossible *)
        failwith "impossible match case"
    in
    (* Use expected type to determine expected key and value types *)
    let (env, kexpected, vexpected, th) =
      match th with
      | Some ((_, tk), (_, tv)) ->
        let (env, tk, tk_expected) = localize_targ env tk in
        let (env, tv, tv_expected) = localize_targ env tv in
        let env = check_collection_tparams env name [fst tk; fst tv] in
        (env, Some tk_expected, Some tv_expected, Some (tk, tv))
      | _ ->
        (* no explicit typehint, fallback to supplied expect *)
        begin
          match expand_expected_and_get_node env expected with
          | (env, Some (pos, reason, ety, _)) ->
            begin
              match get_expected_kind ety with
              | Some (kty, vty) ->
                let k_expected = ExpectedTy.make pos reason kty in
                let v_expected = ExpectedTy.make pos reason vty in
                (env, Some k_expected, Some v_expected, None)
              | None -> (env, None, None, None)
            end
          | _ -> (env, None, None, None)
        end
    in
    let (kl, vl) = List.unzip l in
    let r = Reason.Rtype_variable_generics (p, "Tk", strip_ns name) in
    let (env, tkl, k) =
      compute_exprs_and_supertype
        ~expected:kexpected
        ~use_pos:p
        ~reason:(Reason.URkey name)
        ~bound:(Some (MakeType.arraykey r))
        ~coerce_for_op:true
        r
        env
        kl
        (arraykey_value p name false)
    in
    let (env, tvl, v) =
      compute_exprs_and_supertype
        ~expected:vexpected
        ~use_pos:p
        ~reason:(Reason.URvalue name)
        ~can_pessimise:true
        ~coerce_for_op:false
        ~bound:None
        (Reason.Rtype_variable_generics (p, "Tv", strip_ns name))
        env
        vl
        array_value
    in
    let pairs = List.zip_exn tkl tvl in
    make_result env p (make_expr th pairs) (make_ty k v)
  | Clone e ->
    let (env, te, ty) = expr env e in
    (* Clone only works on objects; anything else fatals at runtime.
     * Constructing a call `e`->__clone() checks that `e` is an object and
     * checks coeffects on __clone *)
    let (_, pe, _) = e in
    let (env, (tfty, _tal)) =
      TOG.obj_get
        ~obj_pos:pe
        ~is_method:true
        ~inst_meth:false
        ~meth_caller:false
        ~nullsafe:None
        ~coerce_from_ty:None
        ~explicit_targs:[]
        ~class_id:(CIexpr e)
        ~member_id:(p, SN.Members.__clone)
        ~on_error:Errors.unify_error
        env
        ty
    in
    let (env, (_tel, _typed_unpack_element, _ty, _should_forget_fakes)) =
      call ~expected:None p env tfty [] None
    in
    make_result env p (Aast.Clone te) ty
  | This ->
    if Option.is_none (Env.get_self_ty env) then Errors.this_var_outside_class p;
    if Env.is_in_expr_tree env then Errors.this_var_in_expr_tree p;
    if not accept_using_var then check_escaping_var env (p, this);
    let ty = Env.get_local env this in
    let r = Reason.Rwitness p in
    let ty = mk (r, get_node ty) in
    make_result env p Aast.This ty
  | True -> make_result env p Aast.True (MakeType.bool (Reason.Rwitness p))
  | False -> make_result env p Aast.False (MakeType.bool (Reason.Rwitness p))
  (* TODO TAST: consider checking that the integer is in range. Right now
   * it's possible for HHVM to fail on well-typed Hack code
   *)
  | Int s -> make_result env p (Aast.Int s) (MakeType.int (Reason.Rwitness p))
  | Float s ->
    make_result env p (Aast.Float s) (MakeType.float (Reason.Rwitness p))
  (* TODO TAST: consider introducing a "null" type, and defining ?t to
   * be null | t
   *)
  | Null -> make_result env p Aast.Null (MakeType.null (Reason.Rwitness p))
  | String s ->
    make_result env p (Aast.String s) (MakeType.string (Reason.Rwitness p))
  | String2 idl ->
    let (env, tel) = string2 env idl in
    make_result env p (Aast.String2 tel) (MakeType.string (Reason.Rwitness p))
  | PrefixedString (n, e) ->
    if String.( <> ) n "re" then (
      Errors.experimental_feature
        p
        "String prefixes other than `re` are not yet supported.";
      expr_error env Reason.Rnone outer
    ) else
      let (env, te, ty) = expr env e in
      let (_, pe, expr_) = e in
      let env = Typing_substring.sub_string pe env ty in
      (match expr_ with
      | String _ ->
        begin
          try
            make_result
              env
              p
              (Aast.PrefixedString (n, te))
              (Typing_regex.type_pattern e)
          with
          | Pcre.Error (Pcre.BadPattern (s, i)) ->
            let s = s ^ " [" ^ string_of_int i ^ "]" in
            Errors.bad_regex_pattern pe s;
            expr_error env (Reason.Rregex pe) e
          | Typing_regex.Empty_regex_pattern ->
            Errors.bad_regex_pattern pe "This pattern is empty";
            expr_error env (Reason.Rregex pe) e
          | Typing_regex.Missing_delimiter ->
            Errors.bad_regex_pattern pe "Missing delimiter(s)";
            expr_error env (Reason.Rregex pe) e
          | Typing_regex.Invalid_global_option ->
            Errors.bad_regex_pattern pe "Invalid global option(s)";
            expr_error env (Reason.Rregex pe) e
        end
      | String2 _ ->
        Errors.re_prefixed_non_string pe "Strings with embedded expressions";
        expr_error env (Reason.Rregex pe) e
      | _ ->
        Errors.re_prefixed_non_string pe "Non-strings";
        expr_error env (Reason.Rregex pe) e)
  | Fun_id x ->
    let (env, fty, _tal) = fun_type_of_id env x [] [] in
    make_result env p (Aast.Fun_id x) fty
  | Id ((cst_pos, cst_name) as id) ->
    (match Env.get_gconst env cst_name with
    | None ->
      Errors.unbound_global cst_pos;
      let ty = err_witness env cst_pos in
      make_result env cst_pos (Aast.Id id) ty
    | Some const ->
      let (env, ty) =
        Phase.localize_no_subst env ~ignore_errors:true const.cd_type
      in
      make_result env p (Aast.Id id) ty)
  | Method_id (instance, meth) ->
    (* Method_id is used when creating a "method pointer" using the magic
     * inst_meth function.
     *
     * Typing this is pretty simple, we just need to check that instance->meth
     * is public+not static and then return its type.
     *)
    let (env, te, ty1) = expr env instance in
    let (env, (result, _tal)) =
      TOG.obj_get
        ~inst_meth:true
        ~meth_caller:false
        ~obj_pos:p
        ~is_method:true
        ~nullsafe:None
        ~coerce_from_ty:None
        ~explicit_targs:[]
        ~class_id:(CIexpr instance)
        ~member_id:meth
        ~on_error:Errors.unify_error
        env
        ty1
    in
    let (env, result) =
      Env.FakeMembers.check_instance_invalid env instance (snd meth) result
    in
    make_result env p (Aast.Method_id (te, meth)) result
  | Method_caller (((pos, class_name) as pos_cname), meth_name) ->
    (* meth_caller('X', 'foo') desugars to:
     * $x ==> $x->foo()
     *)
    let class_ = Env.get_class env class_name in
    (match class_ with
    | None -> unbound_name env pos_cname outer
    | Some class_ ->
      (* Create a class type for the given object instantiated with unresolved
       * types for its type parameters.
       *)
      let () =
        if Ast_defs.is_c_trait (Cls.kind class_) then
          Errors.meth_caller_trait pos class_name
      in
      let (env, tvarl) =
        List.map_env env (Cls.tparams class_) ~f:(fun env _ ->
            Env.fresh_type env p)
      in
      let params =
        List.map (Cls.tparams class_) ~f:(fun { tp_name = (p, n); _ } ->
            (* TODO(T69551141) handle type arguments for Tgeneric *)
            MakeType.generic (Reason.Rwitness_from_decl p) n)
      in
      let obj_type =
        MakeType.apply
          (Reason.Rwitness_from_decl (Pos_or_decl.of_raw_pos p))
          (Positioned.of_raw_positioned pos_cname)
          params
      in
      let ety_env =
        {
          (empty_expand_env_with_on_error (Errors.invalid_type_hint pos)) with
          substs = TUtils.make_locl_subst_for_class_tparams class_ tvarl;
        }
      in
      let (env, local_obj_ty) = Phase.localize ~ety_env env obj_type in
      let (env, (fty, _tal)) =
        TOG.obj_get
          ~obj_pos:pos
          ~is_method:true
          ~nullsafe:None
          ~inst_meth:false
          ~meth_caller:true
          ~coerce_from_ty:None
          ~explicit_targs:[]
          ~class_id:(CI (pos, class_name))
          ~member_id:meth_name
          ~on_error:Errors.unify_error
          env
          local_obj_ty
      in
      let (env, fty) = Env.expand_type env fty in
      (match deref fty with
      | (reason, Tfun ftype) ->
        (* We are creating a fake closure:
         * function(Class $x, arg_types_of(Class::meth_name))
                 : return_type_of(Class::meth_name)
         *)
        let ety_env =
          {
            ety_env with
            on_error = Env.unify_error_assert_primary_pos_in_current_decl env;
          }
        in
        let env =
          Phase.check_tparams_constraints
            ~use_pos:p
            ~ety_env
            env
            (Cls.tparams class_)
        in
        let local_obj_fp = TUtils.default_fun_param local_obj_ty in
        let fty = { ftype with ft_params = local_obj_fp :: ftype.ft_params } in
        let caller =
          {
            ft_arity = fty.ft_arity;
            ft_tparams = fty.ft_tparams;
            ft_where_constraints = fty.ft_where_constraints;
            ft_params = fty.ft_params;
            ft_implicit_params = fty.ft_implicit_params;
            ft_ret = fty.ft_ret;
            ft_flags = fty.ft_flags;
            ft_ifc_decl = fty.ft_ifc_decl;
          }
        in
        make_result
          env
          p
          (Aast.Method_caller (pos_cname, meth_name))
          (mk (reason, Tfun caller))
      | _ ->
        (* This can happen if the method lives in PHP *)
        make_result
          env
          p
          (Aast.Method_caller (pos_cname, meth_name))
          (Typing_utils.mk_tany env pos)))
  | FunctionPointer (FP_class_const (cid, meth), targs) ->
    let (env, _, ce, cty) = class_expr env [] cid in
    let (env, (fpty, tal)) =
      class_get
        ~is_method:true
        ~is_const:false
        ~incl_tc:false (* What is this? *)
        ~coerce_from_ty:None (* What is this? *)
        ~explicit_targs:targs
        ~is_function_pointer:true
        env
        cty
        meth
        cid
    in
    let env = Env.set_tyvar_variance env fpty in
    let fpty = set_function_pointer fpty in
    make_result
      env
      p
      (Aast.FunctionPointer (FP_class_const (ce, meth), tal))
      fpty
  | Smethod_id (((_, pc, cid_) as cid), meth) ->
    (* Smethod_id is used when creating a "method pointer" using the magic
     * class_meth function.
     *
     * Typing this is pretty simple, we just need to check that c::meth is
     * public+static and then return its type.
     *)
    let (class_, classname) =
      match cid_ with
      | CIself
      | CIstatic ->
        (Env.get_self_class env, Env.get_self_id env)
      | CI (_, const) when String.equal const SN.PseudoConsts.g__CLASS__ ->
        (Env.get_self_class env, Env.get_self_id env)
      | CI (_, id) -> (Env.get_class env id, Some id)
      | _ -> (None, None)
    in
    let classname = Option.value classname ~default:"" in
    (match class_ with
    | None ->
      (* The class given as a static string was not found. *)
      unbound_name env (pc, classname) outer
    | Some class_ ->
      let smethod = Env.get_static_member true env class_ (snd meth) in
      (match smethod with
      | None ->
        (* The static method wasn't found. *)
        TOG.smember_not_found
          (fst meth)
          ~is_const:false
          ~is_method:true
          ~is_function_pointer:false
          class_
          (snd meth)
          Errors.unify_error;
        expr_error env Reason.Rnone outer
      | Some ({ ce_type = (lazy ty); ce_pos = (lazy ce_pos); _ } as ce) ->
        let () =
          if get_ce_abstract ce then
            match cid_ with
            | CIstatic -> ()
            | _ -> Errors.class_meth_abstract_call classname (snd meth) p ce_pos
        in
        let ce_visibility = ce.ce_visibility in
        let ce_deprecated = ce.ce_deprecated in
        let (env, _tal, te, cid_ty) = class_expr ~exact:Exact env [] cid in
        let (env, cid_ty) = Env.expand_type env cid_ty in
        let tyargs =
          match get_node cid_ty with
          | Tclass (_, _, tyargs) -> tyargs
          | _ -> []
        in
        let ety_env =
          {
            empty_expand_env with
            substs = TUtils.make_locl_subst_for_class_tparams class_ tyargs;
            this_ty = cid_ty;
          }
        in
        let r = get_reason ty |> Typing_reason.localize in
        (match get_node ty with
        | Tfun ft ->
          let ft =
            Typing_enforceability.compute_enforced_and_pessimize_fun_type env ft
          in
          let def_pos = ce_pos in
          let (env, tal) =
            Phase.localize_targs
              ~check_well_kinded:true
              ~is_method:true
              ~def_pos:ce_pos
              ~use_pos:p
              ~use_name:(strip_ns (snd meth))
              env
              ft.ft_tparams
              []
          in
          let (env, ft) =
            Phase.(
              localize_ft
                ~instantiation:
                  Phase.
                    {
                      use_name = strip_ns (snd meth);
                      use_pos = p;
                      explicit_targs = tal;
                    }
                ~ety_env
                ~def_pos:ce_pos
                env
                ft)
          in
          let ty = mk (r, Tfun ft) in
          let use_pos = fst meth in
          TVis.check_deprecated ~use_pos ~def_pos ce_deprecated;
          (match ce_visibility with
          | Vpublic
          | Vinternal _ ->
            make_result env p (Aast.Smethod_id (te, meth)) ty
          | Vprivate _ ->
            Errors.private_class_meth ~def_pos ~use_pos;
            expr_error env r outer
          | Vprotected _ ->
            Errors.protected_class_meth ~def_pos ~use_pos;
            expr_error env r outer)
        | _ ->
          Errors.internal_error p "We have a method which isn't callable";
          expr_error env r outer)))
  | Lplaceholder p ->
    let r = Reason.Rplaceholder p in
    let ty = MakeType.void r in
    make_result env p (Aast.Lplaceholder p) ty
  | Dollardollar _ when is_lvalue valkind ->
    Errors.dollardollar_lvalue p;
    expr_error env (Reason.Rwitness p) outer
  | Dollardollar id ->
    let ty = Env.get_local_check_defined env id in
    let env = might_throw env in
    make_result env p (Aast.Dollardollar id) ty
  | Lvar ((_, x) as id) ->
    if not accept_using_var then check_escaping_var env id;
    let ty =
      if check_defined then
        Env.get_local_check_defined env id
      else
        Env.get_local env x
    in
    make_result env p (Aast.Lvar id) ty
  | Tuple el ->
    let (env, expected) = expand_expected_and_get_node env expected in
    let (env, tel, tyl) =
      match expected with
      | Some (pos, ur, _, Ttuple expected_tyl) ->
        let (env, pess_expected_tyl) =
          if TypecheckerOptions.pessimise_builtins (Env.get_tcopt env) then
            List.map_env env expected_tyl ~f:Typing_array_access.pessimise_type
          else
            (env, expected_tyl)
        in
        exprs_expected (pos, ur, pess_expected_tyl) env el
      | _ -> exprs env el
    in
    let (env, pess_tyl) =
      if TypecheckerOptions.pessimise_builtins (Env.get_tcopt env) then
        List.map_env env tyl ~f:(Typing_array_access.pessimised_tup_assign p)
      else
        (env, tyl)
    in
    let ty = MakeType.tuple (Reason.Rwitness p) pess_tyl in
    make_result env p (Aast.Tuple tel) ty
  | List el ->
    let (env, tel, tyl) =
      match valkind with
      | `lvalue
      | `lvalue_subexpr ->
        lvalues env el
      | `other ->
        let (env, expected) = expand_expected_and_get_node env expected in
        (match expected with
        | Some (pos, ur, _, Ttuple expected_tyl) ->
          exprs_expected (pos, ur, expected_tyl) env el
        | _ -> exprs env el)
    in
    let ty = MakeType.tuple (Reason.Rwitness p) tyl in
    make_result env p (Aast.List tel) ty
  | Pair (th, e1, e2) ->
    let (env, expected1, expected2, th) =
      match th with
      | Some ((_, t1), (_, t2)) ->
        let (env, t1, t1_expected) = localize_targ env t1 in
        let (env, t2, t2_expected) = localize_targ env t2 in
        (env, Some t1_expected, Some t2_expected, Some (t1, t2))
      | None ->
        (* Use expected type to determine expected element types *)
        (match expand_expected_and_get_node env expected with
        | (env, Some (pos, reason, _ty, Tclass ((_, k), _, [ty1; ty2])))
          when String.equal k SN.Collections.cPair ->
          let ty1_expected = ExpectedTy.make pos reason ty1 in
          let ty2_expected = ExpectedTy.make pos reason ty2 in
          (env, Some ty1_expected, Some ty2_expected, None)
        | _ -> (env, None, None, None))
    in
    let (env, te1, ty1) = expr ?expected:expected1 env e1 in
    let (env, te2, ty2) = expr ?expected:expected2 env e2 in
    let (_, p1, _) = e1 in
    let (_, p2, _) = e2 in
    let (env, ty1, err_opt1) =
      compute_supertype
        ~expected:expected1
        ~reason:Reason.URpair_value
        ~use_pos:p1
        (Reason.Rtype_variable_generics (p1, "T1", "Pair"))
        env
        [ty1]
    in
    let (env, ty2, err_opt2) =
      compute_supertype
        ~expected:expected2
        ~reason:Reason.URpair_value
        ~use_pos:p2
        (Reason.Rtype_variable_generics (p2, "T2", "Pair"))
        env
        [ty2]
    in
    let ty = MakeType.pair (Reason.Rwitness p) ty1 ty2 in
    make_result
      env
      p
      (Aast.Pair
         ( th,
           hole_on_err te1 ~err_opt:(Option.join @@ List.hd err_opt1),
           hole_on_err te2 ~err_opt:(Option.join @@ List.hd err_opt2) ))
      ty
  | Array_get (e, None) ->
    let (env, te, _) = update_array_type p env e valkind in
    let env = might_throw env in
    (* NAST check reports an error if [] is used for reading in an
         lvalue context. *)
    let ty = err_witness env p in
    make_result env p (Aast.Array_get (te, None)) ty
  | Array_get (e1, Some e2) ->
    let (env, te1, ty1) =
      update_array_type ?lhs_of_null_coalesce p env e1 valkind
    in
    let (env, te2, ty2) = expr env e2 in
    let env = might_throw env in
    let is_lvalue = is_lvalue valkind in
    let (_, p1, _) = e1 in
    let (env, ty, arr_err_opt, key_err_opt) =
      Typing_array_access.array_get
        ~array_pos:p1
        ~expr_pos:p
        ?lhs_of_null_coalesce
        is_lvalue
        env
        ty1
        e2
        ty2
    in
    make_result
      env
      p
      (Aast.Array_get
         ( hole_on_err ~err_opt:arr_err_opt te1,
           Some (hole_on_err ~err_opt:key_err_opt te2) ))
      ty
  | Call ((_, pos_id, Id ((_, s) as id)), explicit_targs, el, None)
    when Hash_set.mem typing_env_pseudofunctions s ->
    let (env, tel, tys) =
      argument_list_exprs (expr ~accept_using_var:true) env el
    in
    let env =
      if String.equal s SN.PseudoFunctions.hh_expect then
        do_hh_expect ~equivalent:false env pos_id explicit_targs p tys
      else if String.equal s SN.PseudoFunctions.hh_expect_equivalent then
        do_hh_expect ~equivalent:true env pos_id explicit_targs p tys
      else if not (List.is_empty explicit_targs) then (
        Errors.expected_tparam
          ~definition_pos:Pos_or_decl.none
          ~use_pos:pos_id
          0
          None;
        env
      ) else if String.equal s SN.PseudoFunctions.hh_show then (
        List.iter tys ~f:(Typing_log.hh_show p env);
        env
      ) else if String.equal s SN.PseudoFunctions.hh_show_env then (
        Typing_log.hh_show_env p env;
        env
      ) else if String.equal s SN.PseudoFunctions.hh_log_level then
        match el with
        | [
         (Ast_defs.Pnormal, (_, _, String key_str));
         (Ast_defs.Pnormal, (_, _, Int level_str));
        ] ->
          Env.set_log_level env key_str (int_of_string level_str)
        | _ -> env
      else if String.equal s SN.PseudoFunctions.hh_force_solve then
        Typing_solver.solve_all_unsolved_tyvars env
      else if String.equal s SN.PseudoFunctions.hh_loop_forever then
        let _ = loop_forever env in
        env
      else
        env
    in
    let ty = MakeType.void (Reason.Rwitness p) in
    make_result
      env
      p
      (Aast.Call
         ( Tast.make_typed_expr pos_id (TUtils.mk_tany env pos_id) (Aast.Id id),
           [],
           tel,
           None ))
      ty
  | Call (e, explicit_targs, el, unpacked_element) ->
    let env = might_throw env in
    let ((env, te, ty), should_forget_fakes) =
      dispatch_call
        ~is_using_clause
        ~expected
        ?in_await
        p
        env
        e
        explicit_targs
        el
        unpacked_element
    in
    let env =
      if should_forget_fakes then
        Env.forget_members env Reason.(Blame (p, BScall))
      else
        env
    in
    (env, te, ty)
  | FunctionPointer (FP_id fid, targs) ->
    let (env, fty, targs) = fun_type_of_id env fid targs [] in
    let e = Aast.FunctionPointer (FP_id fid, targs) in
    let fty = set_function_pointer fty in
    make_result env p e fty
  | Binop (Ast_defs.QuestionQuestion, e1, e2) ->
    let (_, e1_pos, _) = e1 in
    let (_, e2_pos, _) = e2 in
    let (env, te1, ty1) =
      raw_expr ~lhs_of_null_coalesce:true env e1 ~allow_awaitable:true
    in
    let (env, te2, ty2) = expr ?expected env e2 ~allow_awaitable:true in
    let (env, ty1') = Env.fresh_type env e1_pos in
    let env =
      SubType.sub_type
        env
        ty1
        (MakeType.nullable_locl Reason.Rnone ty1')
        (Errors.unify_error_at e1_pos)
    in
    (* Essentially mimic a call to
     *   function coalesce<Tr, Ta as Tr, Tb as Tr>(?Ta, Tb): Tr
     * That way we let the constraint solver take care of the union logic.
     *)
    let (env, ty_result) = Env.fresh_type env e2_pos in
    let env = SubType.sub_type env ty1' ty_result (Errors.unify_error_at p) in
    let env = SubType.sub_type env ty2 ty_result (Errors.unify_error_at p) in
    make_result
      env
      p
      (Aast.Binop (Ast_defs.QuestionQuestion, te1, te2))
      ty_result
  | Binop (Ast_defs.Eq op_opt, e1, e2) ->
    let make_result env p te ty =
      let (env, te, ty) = make_result env p te ty in
      let env = Typing_local_ops.check_assignment env te in
      (env, te, ty)
    in
    (match op_opt with
    (* For example, e1 += e2. This is typed and translated as if
     * written e1 = e1 + e2.
     * TODO TAST: is this right? e1 will get evaluated more than once
     *)
    | Some op ->
      let (_, _, expr_) = e1 in
      (match (op, expr_) with
      | (Ast_defs.QuestionQuestion, Class_get _) ->
        Errors.experimental_feature
          p
          "null coalesce assignment operator with static properties";
        expr_error env Reason.Rnone outer
      | _ ->
        let e_fake =
          ((), p, Binop (Ast_defs.Eq None, e1, ((), p, Binop (op, e1, e2))))
        in
        let (env, te_fake, ty) = raw_expr env e_fake in
        let te_opt = resugar_binop te_fake in
        begin
          match te_opt with
          | Some (_, _, te) -> make_result env p te ty
          | _ -> assert false
        end)
    | None ->
      let (env, te2, ty2) = raw_expr env e2 in
      let (_, pos2, _) = te2 in
      let (env, te1, ty, err_opt) = assign p env e1 pos2 ty2 in
      let te = Aast.Binop (Ast_defs.Eq None, te1, hole_on_err ~err_opt te2) in
      make_result env p te ty)
  | Binop (((Ast_defs.Ampamp | Ast_defs.Barbar) as bop), e1, e2) ->
    let c = Ast_defs.(equal_bop bop Ampamp) in
    let (env, te1, _) = expr env e1 in
    let lenv = env.lenv in
    let (env, _lset) = condition env c te1 in
    let (env, te2, _) = expr env e2 in
    let env = { env with lenv } in
    make_result
      env
      p
      (Aast.Binop (bop, te1, te2))
      (MakeType.bool (Reason.Rlogic_ret p))
  | Binop (bop, e1, e2) ->
    let (env, te1, ty1) = raw_expr env e1 in
    let (env, te2, ty2) = raw_expr env e2 in
    let env =
      match bop with
      (* TODO: This could be less conservative: we only need to account for
       * the possibility of exception if the operator is `/` or `/=`.
       *)
      | Ast_defs.Eqeqeq
      | Ast_defs.Diff2 ->
        env
      | _ -> might_throw env
    in
    let (_, p1, _) = e1 in
    let (_, p2, _) = e2 in
    let (env, te3, ty) =
      Typing_arithmetic.binop p env bop p1 te1 ty1 p2 te2 ty2
    in
    (env, te3, ty)
  | Pipe (e0, e1, e2) ->
    (* If it weren't for local variable assignment or refinement the pipe
     * expression e1 |> e2 could be typed using this rule (E is environment with
     * types for locals):
     *
     *    E |- e1 : ty1    E[$$:ty1] |- e2 : ty2
     *    --------------------------------------
     *                E |- e1|>e2 : ty2
     *
     * The possibility of e2 changing the types of locals in E means that E
     * can evolve, and so we need to restore $$ to its original state.
     *)
    let (env, te1, ty1) = expr env e1 in
    let dd_var = Local_id.make_unscoped SN.SpecialIdents.dollardollar in
    let dd_old_ty =
      if Env.is_local_defined env dd_var then
        Some (Env.get_local_pos env dd_var)
      else
        None
    in
    let env = Env.set_local env dd_var ty1 Pos.none in
    let (env, te2, ty2) = expr env e2 ~allow_awaitable:true in
    let env =
      match dd_old_ty with
      | None -> Env.unset_local env dd_var
      | Some (ty, pos) -> Env.set_local env dd_var ty pos
    in
    let (env, te, ty) = make_result env p (Aast.Pipe (e0, te1, te2)) ty2 in
    (env, te, ty)
  | Unop (uop, e) ->
    let (env, te, ty) = raw_expr env e in
    let env = might_throw env in
    let (env, tuop, ty) = Typing_arithmetic.unop p env uop te ty in
    let env = Typing_local_ops.check_assignment env te in
    (env, tuop, ty)
  | Eif (c, e1, e2) -> eif env ~expected ?in_await p c e1 e2
  | Class_const ((_, p, CI sid), pstr)
    when String.equal (snd pstr) "class" && Env.is_typedef env (snd sid) ->
    begin
      match Env.get_typedef env (snd sid) with
      | Some { td_tparams = tparaml; _ } ->
        (* Typedef type parameters cannot have constraints *)
        let params =
          List.map
            ~f:
              begin
                fun { tp_name = (p, x); _ } ->
                (* TODO(T69551141) handle type arguments for Tgeneric *)
                MakeType.generic (Reason.Rwitness_from_decl p) x
              end
            tparaml
        in
        let p_ = Pos_or_decl.of_raw_pos p in
        let tdef =
          mk
            ( Reason.Rwitness_from_decl p_,
              Tapply (Positioned.of_raw_positioned sid, params) )
        in
        let typename =
          mk
            ( Reason.Rwitness_from_decl p_,
              Tapply ((p_, SN.Classes.cTypename), [tdef]) )
        in
        let (env, tparams) =
          List.map_env env tparaml ~f:(fun env _tp -> Env.fresh_type env p)
        in
        let ety_env =
          {
            (empty_expand_env_with_on_error
               (Env.invalid_type_hint_assert_primary_pos_in_current_decl env))
            with
            substs = Subst.make_locl tparaml tparams;
          }
        in
        let env =
          Phase.check_tparams_constraints ~use_pos:p ~ety_env env tparaml
        in
        let (env, ty) = Phase.localize ~ety_env env typename in
        make_result env p (Class_const ((ty, p, CI sid), pstr)) ty
      | None ->
        (* Should not expect None as we've checked whether the sid is a typedef *)
        expr_error env (Reason.Rwitness p) outer
    end
  | Class_const (cid, mid) -> class_const env p (cid, mid)
  | Class_get (((_, _, cid_) as cid), CGstring mid, prop_or_method)
    when Env.FakeMembers.is_valid_static env cid_ (snd mid) ->
    let (env, local) = Env.FakeMembers.make_static env cid_ (snd mid) p in
    let local = ((), p, Lvar (p, local)) in
    let (env, _, ty) = expr env local in
    let (env, _tal, te, _) = class_expr env [] cid in
    make_result
      env
      p
      (Aast.Class_get (te, Aast.CGstring mid, prop_or_method))
      ty
  | Class_get
      (((_, _, cid_) as cid), CGstring ((ppos, _) as mid), prop_or_method) ->
    let (env, _tal, te, cty) = class_expr env [] cid in
    let env = might_throw env in
    let (env, (ty, _tal)) =
      class_get
        ~is_method:false
        ~is_const:false
        ~coerce_from_ty:None
        env
        cty
        mid
        cid
    in
    let (env, ty) =
      Env.FakeMembers.check_static_invalid env cid_ (snd mid) ty
    in
    let env =
      Errors.try_if_no_errors
        (fun () -> Typing_local_ops.enforce_static_property_access ppos env)
        (fun env ->
          let is_lvalue = is_lvalue valkind in
          (* If it's an lvalue we throw an error in a separate check in check_assign *)
          if in_readonly_expr || is_lvalue then
            env
          else
            Typing_local_ops.enforce_mutable_static_variable
              ppos
              env
              (* This msg only appears if we have access to ReadStaticVariables,
                 since otherwise we would have errored in the first function *)
              ~msg:"Please enclose the static in a readonly expression")
    in
    make_result
      env
      p
      (Aast.Class_get (te, Aast.CGstring mid, prop_or_method))
      ty
  (* Fake member property access. For example:
   *   if ($x->f !== null) { ...$x->f... }
   *)
  | Class_get (_, CGexpr _, _) ->
    failwith "AST should not have any CGexprs after naming"
  | Obj_get (e, (_, pid, Id (py, y)), nf, is_prop)
    when Env.FakeMembers.is_valid env e y ->
    let env = might_throw env in
    let (env, local) = Env.FakeMembers.make env e y p in
    let local = ((), p, Lvar (p, local)) in
    let (env, _, ty) = expr env local in
    let (env, t_lhs, _) = expr ~accept_using_var:true env e in
    let t_rhs = Tast.make_typed_expr pid ty (Aast.Id (py, y)) in
    make_result env p (Aast.Obj_get (t_lhs, t_rhs, nf, is_prop)) ty
  (* Statically-known instance property access e.g. $x->f *)
  | Obj_get (e1, (_, pm, Id m), nullflavor, prop_or_method) ->
    let nullsafe =
      match nullflavor with
      | OG_nullthrows -> None
      | OG_nullsafe -> Some p
    in
    let (env, te1, ty1) = expr ~accept_using_var:true env e1 in
    let env = might_throw env in
    (* We typecheck Obj_get by checking whether it is a subtype of
       Thas_member(m, #1) where #1 is a fresh type variable. *)
    let (env, mem_ty) = Env.fresh_type env p in
    let (_, p1, _) = e1 in
    let r = Reason.Rwitness p1 in
    let has_member_ty =
      MakeType.has_member
        r
        ~name:m
        ~ty:mem_ty
        ~class_id:(CIexpr e1)
        ~explicit_targs:None
    in
    let lty1 = LoclType ty1 in
    let (env, result_ty, err_opt) =
      match nullsafe with
      | None ->
        let env_res =
          Type.sub_type_i_res
            p1
            Reason.URnone
            env
            lty1
            has_member_ty
            Errors.unify_error
        in
        let (env, err_opt) =
          Result.fold
            env_res
            ~ok:(fun env -> (env, None))
            ~error:(fun env -> (env, Some (ty1, MakeType.nothing Reason.none)))
        in
        (env, mem_ty, err_opt)
      | Some _ ->
        (* In that case ty1 is a subtype of ?Thas_member(m, #1)
           and the result is ?#1 if ty1 is nullable. *)
        let r = Reason.Rnullsafe_op p in
        let null_ty = MakeType.null r in
        let (env, null_has_mem_ty) =
          Union.union_i env r has_member_ty null_ty
        in
        let env_res =
          Type.sub_type_i_res
            p1
            Reason.URnone
            env
            lty1
            null_has_mem_ty
            Errors.unify_error
        in
        let (env, err_opt) =
          Result.fold
            env_res
            ~ok:(fun env -> (env, None))
            ~error:(fun env -> (env, Some (ty1, MakeType.nothing Reason.none)))
        in
        let (env, null_or_nothing_ty) = Inter.intersect env ~r null_ty ty1 in
        let (env, result_ty) = Union.union env null_or_nothing_ty mem_ty in
        (env, result_ty, err_opt)
    in
    let (env, result_ty) =
      Env.FakeMembers.check_instance_invalid env e1 (snd m) result_ty
    in
    make_result
      env
      p
      (Aast.Obj_get
         ( hole_on_err ~err_opt te1,
           Tast.make_typed_expr pm result_ty (Aast.Id m),
           nullflavor,
           prop_or_method ))
      result_ty
  (* Dynamic instance property access e.g. $x->$f *)
  | Obj_get (e1, e2, nullflavor, prop_or_method) ->
    let (env, te1, ty1) = expr ~accept_using_var:true env e1 in
    let (env, te2, _) = expr env e2 in
    let ty =
      if TUtils.is_dynamic env ty1 then
        MakeType.dynamic (Reason.Rwitness p)
      else
        Typing_utils.mk_tany env p
    in
    let (_, pos, te2) = te2 in
    let env = might_throw env in
    let te2 = Tast.make_typed_expr pos ty te2 in
    make_result env p (Aast.Obj_get (te1, te2, nullflavor, prop_or_method)) ty
  | Yield af ->
    let (env, (taf, opt_key, value)) = array_field ~allow_awaitable env af in
    let Typing_env_return_info.{ return_type = expected_return; _ } =
      Env.get_return env
    in
    let send =
      match get_node expected_return.et_type with
      | Tclass (_, _, _ :: _ :: send :: _) -> send
      | Tdynamic when env.in_support_dynamic_type_method_check ->
        expected_return.et_type
      | _ ->
        Errors.internal_error p "Return type is not a generator";
        Typing_utils.terr env (Reason.Ryield_send p)
    in
    let is_async =
      match Env.get_fn_kind env with
      | Ast_defs.FGenerator -> false
      (* This could also catch sync/async non-generators, but an error would
       * have already been generated elsewhere *)
      | _ -> true
    in
    let (env, key) =
      match (af, opt_key) with
      | (AFvalue (_, p, _), None) ->
        if is_async then
          let (env, ty) = Env.fresh_type env p in
          (env, MakeType.nullable_locl (Reason.Ryield_asyncnull p) ty)
        else
          (env, MakeType.int (Reason.Rwitness p))
      | (_, Some x) -> (env, x)
      | (_, _) -> assert false
    in
    let rty =
      if is_async then
        MakeType.async_generator (Reason.Ryield_asyncgen p) key value send
      else
        MakeType.generator (Reason.Ryield_gen p) key value send
    in
    let Typing_env_return_info.{ return_type = expected_return; _ } =
      Env.get_return env
    in
    let env =
      Typing_coercion.coerce_type
        p
        Reason.URyield
        env
        rty
        expected_return
        Errors.unify_error
    in
    let env = Env.forget_members env Reason.(Blame (p, BScall)) in
    let env = LEnv.save_and_merge_next_in_cont env C.Exit in
    make_result
      env
      p
      (Aast.Yield taf)
      (MakeType.nullable_locl (Reason.Ryield_send p) send)
  | Await e ->
    let env = might_throw env in
    (* Await is permitted in a using clause e.g. using (await make_handle()) *)
    let (env, te, rty) =
      expr
        ~is_using_clause
        ~in_await:(Reason.Rwitness p)
        env
        e
        ~allow_awaitable:true
    in
    let (env, ty) = Async.overload_extract_from_awaitable env ~p rty in
    make_result env p (Aast.Await te) ty
  | ReadonlyExpr e ->
    let env = Env.set_readonly env true in
    let (env, te, rty) = expr ~is_using_clause ~in_readonly_expr:true env e in
    make_result env p (Aast.ReadonlyExpr te) rty
  | New ((_, pos, c), explicit_targs, el, unpacked_element, ()) ->
    let env = might_throw env in
    let ( env,
          tc,
          tal,
          tel,
          typed_unpack_element,
          ty,
          ctor_fty,
          should_forget_fakes ) =
      new_object
        ~expected
        ~is_using_clause
        ~check_parent:false
        ~check_not_abstract:true
        pos
        env
        c
        explicit_targs
        (List.map ~f:(fun e -> (Ast_defs.Pnormal, e)) el)
        unpacked_element
    in
    let env =
      if should_forget_fakes then
        Env.forget_members env Reason.(Blame (p, BScall))
      else
        env
    in
    make_result
      env
      p
      (Aast.New (tc, tal, List.map ~f:snd tel, typed_unpack_element, ctor_fty))
      ty
  | Record ((pos, id), field_values) ->
    (match Decl_provider.get_record_def (Env.get_ctx env) id with
    | Some rd ->
      if rd.rdt_abstract then Errors.new_abstract_record (pos, id);

      let field_name (_, pos, expr_) =
        match expr_ with
        | Aast.String name -> Some (pos, name)
        | _ ->
          (* TODO T44306013: Ensure that other values for field names are banned. *)
          None
      in
      let fields_declared = Typing_helpers.all_record_fields env rd in
      let fields_present =
        List.map field_values ~f:(fun (name, _value) -> field_name name)
        |> List.filter_opt
      in
      (* Check for missing required fields. *)
      let fields_present_names =
        List.map ~f:snd fields_present |> SSet.of_list
      in
      SMap.iter
        (fun field_name info ->
          let ((field_pos, _), req) = info in
          match req with
          | Typing_defs.ValueRequired
            when not (SSet.mem field_name fields_present_names) ->
            Errors.missing_record_field_name
              ~field_name
              ~new_pos:pos
              ~record_name:id
              ~field_decl_pos:field_pos
          | _ -> ())
        fields_declared;

      (* Check for unknown fields.*)
      List.iter fields_present ~f:(fun (pos, field_name) ->
          if not (SMap.mem field_name fields_declared) then
            Errors.unexpected_record_field_name
              ~field_name
              ~field_pos:pos
              ~record_name:id
              ~decl_pos:(fst rd.rdt_name))
    | None -> Errors.type_not_record id pos);

    expr_error env (Reason.Rwitness p) outer
  | Cast (hint, e) ->
    let (env, te, ty2) = expr ?in_await env e in
    let env = might_throw env in
    let env =
      if
        TypecheckerOptions.experimental_feature_enabled
          (Env.get_tcopt env)
          TypecheckerOptions.experimental_forbid_nullable_cast
        && not (TUtils.is_mixed env ty2)
      then
        SubType.sub_type_or_fail
          env
          ty2
          (MakeType.nonnull (get_reason ty2))
          (fun () ->
            Errors.nullable_cast p (Typing_print.error env ty2) (get_pos ty2))
      else
        env
    in
    let (env, ty) =
      Phase.localize_hint_no_subst env ~ignore_errors:false hint
    in
    make_result env p (Aast.Cast (hint, te)) ty
  | ExpressionTree et -> expression_tree env p et
  | Is (e, hint) ->
    let (env, te, _) = expr env e in
    make_result env p (Aast.Is (te, hint)) (MakeType.bool (Reason.Rwitness p))
  | As (e, hint, is_nullable) ->
    let refine_type env lpos lty rty =
      let reason = Reason.Ras lpos in
      let (env, rty) = Env.expand_type env rty in
      refine_and_simplify_intersection env p reason lpos lty rty
    in
    let (env, te, expr_ty) = expr env e in
    let env = might_throw env in
    let (env, hint_ty) =
      Phase.localize_hint_no_subst env ~ignore_errors:false hint
    in
    let enable_sound_dynamic =
      TypecheckerOptions.enable_sound_dynamic env.genv.tcopt
    in
    let (env, hint_ty) =
      if Typing_defs.is_dynamic hint_ty then
        let env =
          if enable_sound_dynamic then
            SubType.sub_type
              ~coerce:(Some Typing_logic.CoerceToDynamic)
              env
              expr_ty
              hint_ty
              (Errors.unify_error_at p)
          else
            env
        in
        let (env, locl) = make_a_local_of ~include_this:true env e in
        let env =
          match locl with
          | Some ivar -> set_local env ivar hint_ty
          | None -> env
        in
        (env, hint_ty)
      else if is_nullable then
        let (_, e_p, _) = e in
        let (env, hint_ty) = refine_type env e_p expr_ty hint_ty in
        (env, MakeType.nullable_locl (Reason.Rwitness p) hint_ty)
      else
        let (env, locl) = make_a_local_of ~include_this:true env e in
        match locl with
        | Some ((ivar_pos, _) as ivar) ->
          let (env, hint_ty) = refine_type env ivar_pos expr_ty hint_ty in
          let env = set_local env ivar hint_ty in
          (env, hint_ty)
        | None ->
          let (_, e_p, _) = e in
          refine_type env e_p expr_ty hint_ty
    in
    make_result env p (Aast.As (te, hint, is_nullable)) hint_ty
  | Upcast (e, hint) ->
    let (env, te, expr_ty) = expr env e in
    let (env, hint_ty) =
      Phase.localize_hint_no_subst env ~ignore_errors:false hint
    in
    let env =
      SubType.sub_type
        ~coerce:(Some Typing_logic.CoerceToDynamic)
        env
        expr_ty
        hint_ty
        (Errors.unify_error_at p)
    in
    make_result env p (Aast.Upcast (te, hint)) hint_ty
  | Efun (f, idl) -> lambda ~is_anon:true ?expected p env f idl
  | Lfun (f, idl) -> lambda ~is_anon:false ?expected p env f idl
  | Xml (sid, attrl, el) ->
    let cid = CI sid in
    let (env, _tal, _te, classes) =
      class_id_for_new ~exact:Nonexact p env cid []
    in
    (* OK to ignore rest of list; class_info only used for errors, and
       * cid = CI sid cannot produce a union of classes anyhow *)
    let class_info =
      List.find_map classes ~f:(function
          | `Dynamic -> None
          | `Class (_, class_info, _) -> Some class_info)
    in
    let (env, te, obj) =
      (* New statements derived from Xml literals are of the following form:
       *
       *   __construct(
       *     darray<string,mixed> $attributes,
       *     varray<mixed> $children,
       *     string $file,
       *     int $line
       *   );
       *)
      let new_exp = Typing_xhp.rewrite_xml_into_new p sid attrl el in
      expr ?expected env new_exp
    in
    let tchildren =
      match te with
      | ( _,
          _,
          New
            ( _,
              _,
              [
                _;
                (_, _, (Varray (_, children) | ValCollection (Vec, _, children)));
                _;
                _;
              ],
              _,
              _ ) ) ->
        (* Typing_xhp.rewrite_xml_into_new generates an AST node for a `varray[]` literal, which is interpreted as a vec[]  *)
        children
      | _ ->
        (* We end up in this case when the cosntructed new expression does
           not typecheck. *)
        []
    in
    let (env, typed_attrs) = xhp_attribute_exprs env class_info attrl sid obj in
    let txml = Aast.Xml (sid, typed_attrs, tchildren) in
    (match class_info with
    | None -> make_result env p txml (TUtils.terr env (Reason.Runknown_class p))
    | Some _ -> make_result env p txml obj)
  | Shape fdm ->
    let expr_helper ?expected env (k, e) =
      let (env, et, ty) = expr ?expected env e in
      if TypecheckerOptions.pessimise_builtins (Env.get_tcopt env) then
        let (env, ty) = Typing_array_access.pessimised_tup_assign p env ty in
        (env, (k, et, ty))
      else
        (env, (k, et, ty))
    in
    let (env, tfdm) =
      match expand_expected_and_get_node env expected with
      | (env, Some (pos, ur, _, Tshape (_, expected_fdm))) ->
        List.map_env
          env
          ~f:(fun env ((k, _) as ke) ->
            let tk = TShapeField.of_ast Pos_or_decl.of_raw_pos k in
            match TShapeMap.find_opt tk expected_fdm with
            | None -> expr_helper env ke
            | Some sft ->
              let (env, ty) =
                Typing_array_access.maybe_pessimise_type env sft.sft_ty
              in
              expr_helper ~expected:(ExpectedTy.make pos ur ty) env ke)
          fdm
      | _ -> List.map_env env ~f:expr_helper fdm
    in
    let fdm =
      List.fold_left
        ~f:(fun acc (k, _, ty) ->
          let tk = TShapeField.of_ast Pos_or_decl.of_raw_pos k in
          TShapeMap.add tk { sft_optional = false; sft_ty = ty } acc)
        ~init:TShapeMap.empty
        tfdm
    in
    let env =
      Typing_shapes.check_shape_keys_validity env (List.map tfdm ~f:fst3)
    in
    (* Fields are fully known, because this shape is constructed
     * using shape keyword and we know exactly what fields are set. *)
    make_result
      env
      p
      (Aast.Shape (List.map ~f:(fun (k, te, _) -> (k, te)) tfdm))
      (mk (Reason.Rwitness p, Tshape (Closed_shape, fdm)))
  | ET_Splice e ->
    Typing_env.with_in_expr_tree env false (fun env -> et_splice env p e)
  | EnumClassLabel (None, s) ->
    let (env, expect_label, lty_opt) =
      match expected with
      | Some ety ->
        let (env, lty) = Env.expand_type env ety.ExpectedTy.ty.et_type in
        let expect_label =
          match get_node lty with
          | Tnewtype (name, _, _) ->
            String.equal SN.Classes.cEnumClassLabel name
          | _ -> false
        in
        (env, expect_label, Some lty)
      | None -> (env, false, None)
    in
    let () =
      if expect_label then
        Errors.enum_class_label_as_expr p
      else
        let reason =
          match lty_opt with
          | Some lty ->
            let r = get_reason lty in
            let expect_str = Typing_print.error env lty in
            Reason.to_string (Format.sprintf "Expected %s" expect_str) r
            @ [
                ( Pos_or_decl.of_raw_pos p,
                  Format.sprintf "But got an enum class label: `#%s`" s );
              ]
          | None ->
            [
              ( Pos_or_decl.of_raw_pos p,
                Format.sprintf "Unexpected enum class label: `#%s`" s );
            ]
        in
        Errors.unify_error (p, "Enum class label/member mismatch") reason
    in
    make_result
      env
      p
      (Aast.EnumClassLabel (None, s))
      (mk (Reason.Rwitness p, Terr))
  | EnumClassLabel ((Some (pos, cname) as e), name) ->
    let (env, res) =
      EnumClassLabelOps.expand
        pos
        env
        ~full:true
        ~ctor:SN.Classes.cEnumClassLabel
        cname
        name
    in
    let error () =
      make_result
        env
        p
        (Aast.EnumClassLabel (e, name))
        (mk (Reason.Rwitness p, Terr))
    in
    (match res with
    | EnumClassLabelOps.Success ((_, _, texpr), lty) ->
      make_result env p texpr lty
    | EnumClassLabelOps.ClassNotFound ->
      (* Error registered in nast_check/unbound_name_check *)
      error ()
    | EnumClassLabelOps.LabelNotFound _ ->
      (* Error registered in EnumClassLabelOps.expand *)
      error ()
    | EnumClassLabelOps.Invalid
    | EnumClassLabelOps.Skip ->
      Errors.enum_class_label_as_expr p;
      error ())

and class_const ?(incl_tc = false) env p (cid, mid) =
  let (env, _tal, ce, cty) = class_expr env [] cid in
  let env =
    match get_node cty with
    | Tclass ((_, n), _, _)
      when Env.is_enum_class env n && String.(SN.Members.mClass <> snd mid) ->
      Typing_local_ops.enforce_enum_class_variant p env
    | _ -> env
  in
  let (env, (const_ty, _tal)) =
    class_get
      ~is_method:false
      ~is_const:true
      ~incl_tc
      ~coerce_from_ty:None
      env
      cty
      mid
      cid
  in
  make_result env p (Aast.Class_const (ce, mid)) const_ty

and get_callable_variadicity env variadicity_decl_ty = function
  | FVvariadicArg vparam ->
    let (env, ty) =
      Typing_param.make_param_local_ty env variadicity_decl_ty vparam
    in
    let (env, t_variadic) = bind_param env (ty, vparam) in
    (env, Aast.FVvariadicArg t_variadic)
  | FVnonVariadic -> (env, Aast.FVnonVariadic)

and function_dynamically_callable
    env f params_decl_ty variadicity_decl_ty ret_locl_ty =
  let env = { env with in_support_dynamic_type_method_check = true } in
  let interface_check =
    Typing_dynamic.sound_dynamic_interface_check
      env
      (variadicity_decl_ty :: params_decl_ty)
      ret_locl_ty
  in
  let function_body_check () =
    (* Here the body of the function is typechecked again to ensure it is safe
     * to call it from a dynamic context (eg. under dyn..dyn->dyn assumptions).
     * The code below must be kept in sync with with the fun_def checks.
     *)
    let make_dynamic pos =
      Typing_make_type.dynamic (Reason.Rsupport_dynamic_type pos)
    in
    let dynamic_return_ty = make_dynamic (get_pos ret_locl_ty) in
    let dynamic_return_info =
      Typing_env_return_info.
        {
          return_type = MakeType.unenforced dynamic_return_ty;
          return_disposable = false;
          return_explicit = true;
          return_dynamically_callable = true;
        }
    in
    let (env, param_tys) =
      List.zip_exn f.f_params params_decl_ty
      |> List.map_env env ~f:(fun env (param, hint) ->
             let dyn_ty =
               make_dynamic @@ Pos_or_decl.of_raw_pos param.param_pos
             in
             let ty =
               match hint with
               | Some ty when Typing_enforceability.is_enforceable env ty ->
                 Typing_make_type.intersection
                   (Reason.Rsupport_dynamic_type Pos_or_decl.none)
                   [ty; dyn_ty]
               | _ -> dyn_ty
             in
             Typing_param.make_param_local_ty env (Some ty) param)
    in
    let params_need_immutable = Typing_coeffects.get_ctx_vars f.f_ctxs in
    let (env, _) =
      (* In this pass, bind_param_and_check receives a pair where the lhs is
       * either Tdynamic or TInstersection of the original type and TDynamic,
       * but the fun_param is still referencing the source hint. We amend
       * the source hint to keep in in sync before calling bind_param
       * so the right enforcement is computed.
       *)
      let bind_param_and_check env lty_and_param =
        let (ty, param) = lty_and_param in
        let name = param.param_name in
        let (hi, hopt) = param.param_type_hint in
        let hopt =
          Option.map hopt ~f:(fun (p, h) ->
              if Typing_utils.is_tintersection env ty then
                (p, Hintersection [(p, h); (p, Hdynamic)])
              else
                (p, Hdynamic))
        in
        let param_type_hint = (hi, hopt) in
        let param = (ty, { param with param_type_hint }) in
        let immutable =
          List.exists ~f:(String.equal name) params_need_immutable
        in
        let (env, fun_param) = bind_param ~immutable env param in
        (env, fun_param)
      in
      List.map_env
        env
        (List.zip_exn param_tys f.f_params)
        ~f:bind_param_and_check
    in

    let pos = fst f.f_name in
    let (env, t_variadic) =
      get_callable_variadicity
        env
        (Some (make_dynamic @@ Pos_or_decl.of_raw_pos pos))
        f.f_variadic
    in
    let env =
      set_tyvars_variance_in_callable env dynamic_return_ty param_tys t_variadic
    in
    let disable =
      Naming_attributes.mem
        SN.UserAttributes.uaDisableTypecheckerInternal
        f.f_user_attributes
    in

    Errors.try_
      (fun () ->
        let (_ : env * Tast.stmt list) =
          fun_ ~disable env dynamic_return_info pos f.f_body f.f_fun_kind
        in
        ())
      (fun error ->
        Errors.function_is_not_dynamically_callable pos (snd f.f_name) error)
  in
  if not interface_check then function_body_check ()

and lambda ~is_anon ?expected p env f idl =
  (* This is the function type as declared on the lambda itself.
   * If type hints are absent then use Tany instead. *)
  let declared_fe = Decl_nast.lambda_decl_in_env env.decl_env f in
  let { fe_type; fe_pos; _ } = declared_fe in
  let (declared_pos, declared_ft) =
    match get_node fe_type with
    | Tfun ft -> (fe_pos, ft)
    | _ -> failwith "Not a function"
  in
  let declared_decl_ft =
    Typing_enforceability.compute_enforced_and_pessimize_fun_type
      env
      declared_ft
  in
  (* When creating a closure, the 'this' type will mean the late bound type
   * of the current enclosing class
   *)
  let ety_env =
    empty_expand_env_with_on_error
      (Env.invalid_type_hint_assert_primary_pos_in_current_decl env)
  in
  let (env, declared_ft) =
    Phase.(
      localize_ft
        ~instantiation:{ use_name = "lambda"; use_pos = p; explicit_targs = [] }
        ~ety_env
        ~def_pos:declared_pos
        env
        declared_decl_ft)
  in
  List.iter idl ~f:(check_escaping_var env);

  (* Ensure lambda arity is not ellipsis in strict mode *)
  begin
    match declared_ft.ft_arity with
    | Fvariadic { fp_name = None; _ } ->
      Errors.ellipsis_strict_mode ~require:`Param_name p
    | _ -> ()
  end;

  (* Is the return type declared? *)
  let is_explicit_ret = Option.is_some (hint_of_type_hint f.f_ret) in
  let check_body_under_known_params env ?ret_ty ft : env * _ * locl_ty =
    let (env, (tefun, ty, ft)) =
      closure_make ?ret_ty env p declared_decl_ft f ft idl is_anon
    in
    let inferred_ty =
      mk
        ( Reason.Rwitness p,
          Tfun
            {
              ft with
              ft_ret =
                (if is_explicit_ret then
                  declared_ft.ft_ret
                else
                  MakeType.unenforced ty);
            } )
    in
    (env, tefun, inferred_ty)
  in
  let (env, eexpected) = expand_expected_and_get_node env expected in
  match eexpected with
  | Some (_pos, _ur, tdyn, Tdynamic)
    when env.in_support_dynamic_type_method_check ->
    let make_dynamic { et_type; et_enforced } =
      let et_type =
        match (get_node et_type, et_enforced) with
        | (Tany _, _) (* lambda param without type hint *)
        | (_, Unenforced) ->
          tdyn
        | (_, Enforced) ->
          MakeType.intersection
            (Reason.Rsupport_dynamic_type (get_pos tdyn))
            [tdyn; et_type]
      in
      { et_type; et_enforced }
    in
    let ft_params =
      List.map declared_ft.ft_params ~f:(function param ->
          { param with fp_type = make_dynamic param.fp_type })
    in
    check_body_under_known_params
      env
      ~ret_ty:tdyn
      { declared_ft with ft_params }
  | Some (_pos, _ur, ty, Tfun expected_ft) ->
    (* First check that arities match up *)
    check_lambda_arity p (get_pos ty) declared_ft expected_ft;
    (* Use declared types for parameters in preference to those determined
     * by the context (expected parameters): they might be more general. *)
    let rec replace_non_declared_types declared_ft_params expected_ft_params =
      match (declared_ft_params, expected_ft_params) with
      | ( declared_ft_param :: declared_ft_params,
          expected_ft_param :: expected_ft_params ) ->
        let rest =
          replace_non_declared_types declared_ft_params expected_ft_params
        in
        (* If the type parameter did not have a type hint, it is Tany and
           we use the expected type instead. Otherwise, declared type takes
           precedence. *)
        let resolved_ft_param =
          if TUtils.is_any env declared_ft_param.fp_type.et_type then
            { declared_ft_param with fp_type = expected_ft_param.fp_type }
          else
            declared_ft_param
        in
        resolved_ft_param :: rest
      | (_, []) ->
        (* Morally, this case should match on ([],[]) because we already
           check arity mismatch between declared and expected types. We
           handle it more generally here to be graceful. *)
        declared_ft_params
      | ([], _) ->
        (* This means the expected_ft params list can have more parameters
         * than declared parameters in the lambda. For variadics, this is OK.
         *)
        expected_ft_params
    in
    let replace_non_declared_arity variadic declared_arity expected_arity =
      match variadic with
      | FVvariadicArg { param_type_hint = (_, Some _); _ } -> declared_arity
      | FVvariadicArg _ ->
        begin
          match (declared_arity, expected_arity) with
          | (Fvariadic declared, Fvariadic expected) ->
            Fvariadic { declared with fp_type = expected.fp_type }
          | (_, _) -> declared_arity
        end
      | _ -> declared_arity
    in
    let expected_ft =
      {
        expected_ft with
        ft_arity =
          replace_non_declared_arity
            f.f_variadic
            declared_ft.ft_arity
            expected_ft.ft_arity;
        ft_params =
          replace_non_declared_types declared_ft.ft_params expected_ft.ft_params;
        ft_implicit_params = declared_ft.ft_implicit_params;
        ft_flags = declared_ft.ft_flags;
      }
    in
    (* Don't bother passing in `void` if there is no explicit return *)
    let ret_ty =
      match get_node expected_ft.ft_ret.et_type with
      | Tprim Tvoid when not is_explicit_ret -> None
      | _ -> Some expected_ft.ft_ret.et_type
    in
    Typing_log.increment_feature_count env FL.Lambda.contextual_params;
    check_body_under_known_params env ?ret_ty expected_ft
  | _ ->
    let explicit_variadic_param_or_non_variadic =
      match f.f_variadic with
      | FVvariadicArg { param_type_hint; _ } ->
        Option.is_some (hint_of_type_hint param_type_hint)
      | _ -> true
    in
    (* If all parameters are annotated with explicit types, then type-check
     * the body under those assumptions and pick up the result type *)
    let all_explicit_params =
      List.for_all f.f_params ~f:(fun param ->
          Option.is_some (hint_of_type_hint param.param_type_hint))
    in
    if all_explicit_params && explicit_variadic_param_or_non_variadic then (
      Typing_log.increment_feature_count
        env
        (if List.is_empty f.f_params then
          FL.Lambda.no_params
        else
          FL.Lambda.explicit_params);
      check_body_under_known_params env declared_ft
    ) else (
      match expected with
      | Some ExpectedTy.{ ty = { et_type; _ }; _ } when is_any et_type ->
        (* If the expected type is Tany env then we're passing a lambda to
         * an untyped function and we just assume every parameter has type
         * Tany.
         * Note: we should be using 'nothing' to type the arguments. *)
        Typing_log.increment_feature_count env FL.Lambda.untyped_context;
        check_body_under_known_params env declared_ft
      | Some ExpectedTy.{ ty = { et_type; _ }; _ }
        when TUtils.is_mixed env et_type || is_dynamic et_type ->
        (* If the expected type of a lambda is mixed or dynamic, we
         * decompose the expected type into a function type where the
         * undeclared parameters and the return type are set to the expected
         * type of lambda, i.e., mixed or dynamic.
         *
         * For an expected mixed type, one could argue that the lambda
         * doesn't even need to be checked as it can't be called (there is
         * no downcast to function type). Thus, we should be using nothing
         * to type the arguments. But generally users are very confused by
         * the use of nothing and would expect the lambda body to be
         * checked as though it could be called.
         *)
        let replace_non_declared_type declared_ft_param =
          let is_undeclared =
            TUtils.is_any env declared_ft_param.fp_type.et_type
          in
          if is_undeclared then
            let enforced_ty = { et_enforced = Unenforced; et_type } in
            { declared_ft_param with fp_type = enforced_ty }
          else
            declared_ft_param
        in
        let expected_ft =
          let ft_params =
            List.map ~f:replace_non_declared_type declared_ft.ft_params
          in
          { declared_ft with ft_params }
        in
        let ret_ty = et_type in
        check_body_under_known_params env ~ret_ty expected_ft
      | Some _ ->
        (* If the expected type is something concrete but not a function
         * then we should reject in strict mode. Check body anyway.
         * Note: we should be using 'nothing' to type the arguments. *)
        Errors.untyped_lambda_strict_mode p;
        Typing_log.increment_feature_count
          env
          FL.Lambda.non_function_typed_context;
        check_body_under_known_params env declared_ft
      | None ->
        Typing_log.increment_feature_count env FL.Lambda.fresh_tyvar_params;

        (* Replace uses of Tany that originated from "untyped" parameters or return type
         * with fresh type variables *)
        let freshen_ftype env ft =
          let freshen_ty env pos et =
            match get_node et.et_type with
            | Tany _ ->
              let (env, ty) = Env.fresh_type env pos in
              (env, { et with et_type = ty })
            | Tclass (id, e, [ty])
              when String.equal (snd id) SN.Classes.cAwaitable && is_any ty ->
              let (env, t) = Env.fresh_type env pos in
              ( env,
                {
                  et with
                  et_type = mk (get_reason et.et_type, Tclass (id, e, [t]));
                } )
            | _ -> (env, et)
          in
          let freshen_untyped_param env ft_param =
            let (env, fp_type) =
              freshen_ty
                env
                (Pos_or_decl.unsafe_to_raw_pos ft_param.fp_pos)
                ft_param.fp_type
            in
            (env, { ft_param with fp_type })
          in
          let (env, ft_params) =
            List.map_env env ft.ft_params ~f:freshen_untyped_param
          in
          let (env, ft_ret) = freshen_ty env f.f_span ft.ft_ret in
          (env, { ft with ft_params; ft_ret })
        in
        let (env, declared_ft) = freshen_ftype env declared_ft in
        let env =
          Env.set_tyvar_variance env (mk (Reason.Rnone, Tfun declared_ft))
        in
        (* TODO(jjwu): the declared_ft here is set to public,
           but is actually inferred from the surrounding context
           (don't think this matters in practice, since we check lambdas separately) *)
        check_body_under_known_params
          env
          ~ret_ty:declared_ft.ft_ret.et_type
          declared_ft
    )

(**
 * Process a spread operator by computing the intersection of XHP attributes
 * between the spread expression and the XHP constructor onto which we're
 * spreading.
 *)
and xhp_spread_attribute env c_onto valexpr sid obj =
  let (_, p, _) = valexpr in
  let (env, te, valty) = expr env valexpr ~allow_awaitable:(*?*) false in
  let (env, attr_ptys) =
    match c_onto with
    | None -> (env, [])
    | Some class_info -> Typing_xhp.get_spread_attributes env p class_info valty
  in
  let (env, has_err) =
    List.fold_left
      attr_ptys
      ~f:(fun (env, has_err) attr ->
        let (env, _, err_opt) = xhp_attribute_decl_ty env sid obj attr in
        (env, has_err || Option.is_some err_opt))
      ~init:(env, false)
  in
  (* If we have a subtyping error for any attribute, the best we can do here
     is give an expected type of dynamic *)
  let err_opt =
    if has_err then
      Some (valty, MakeType.nothing Reason.Rnone)
    else
      None
  in
  (* Build the typed attribute node *)
  let typed_attr = Aast.Xhp_spread (hole_on_err ~err_opt te) in
  (env, typed_attr)

(**
 * Simple XHP attributes (attr={expr} form) are simply interpreted as a member
 * variable prefixed with a colon.
 *)
and xhp_simple_attribute env id valexpr sid obj =
  let (_, p, _) = valexpr in
  let (env, te, valty) = expr env valexpr ~allow_awaitable:(*?*) false in
  (* This converts the attribute name to a member name. *)
  let name = ":" ^ snd id in
  let attr_pty = ((fst id, name), (p, valty)) in
  let (env, decl_ty, err_opt) = xhp_attribute_decl_ty env sid obj attr_pty in
  let typed_attr =
    Aast.Xhp_simple
      { xs_name = id; xs_type = decl_ty; xs_expr = hole_on_err ~err_opt te }
  in
  (env, typed_attr)

(**
 * Typecheck the attribute expressions - this just checks that the expressions are
 * valid, not that they match the declared type for the attribute and,
 * in case of spreads, makes sure they are XHP.
 *)
and xhp_attribute_exprs env cls_decl attrl sid obj =
  let handle_attr (env, typed_attrl) attr =
    let (env, typed_attr) =
      match attr with
      | Xhp_simple { xs_name = id; xs_expr = valexpr; _ } ->
        xhp_simple_attribute env id valexpr sid obj
      | Xhp_spread valexpr -> xhp_spread_attribute env cls_decl valexpr sid obj
    in
    (env, typed_attr :: typed_attrl)
  in
  let (env, typed_attrl) =
    List.fold_left ~f:handle_attr ~init:(env, []) attrl
  in
  (env, List.rev typed_attrl)

(*****************************************************************************)
(* Anonymous functions & lambdas. *)
(*****************************************************************************)
and closure_bind_param params (env, t_params) ty : env * Tast.fun_param list =
  match !params with
  | [] ->
    (* This code cannot be executed normally, because the arity is wrong
     * and it will error later. Bind as many parameters as we can and carry
     * on. *)
    (env, t_params)
  | param :: paraml ->
    params := paraml;
    (match hint_of_type_hint param.param_type_hint with
    | Some h ->
      let (pos, _) = h in
      (* When creating a closure, the 'this' type will mean the
       * late bound type of the current enclosing class
       *)
      let (env, h) = Phase.localize_hint_no_subst env ~ignore_errors:false h in
      let env =
        Typing_coercion.coerce_type
          pos
          Reason.URparam
          env
          ty
          (MakeType.unenforced h)
          Errors.unify_error
      in
      (* Closures are allowed to have explicit type-hints. When
       * that is the case we should check that the argument passed
       * is compatible with the type-hint.
       * The body of the function should be type-checked with the
       * hint and not the type of the argument passed.
       * Otherwise it leads to strange results where
       * foo(?string $x = null) is called with a string and fails to
       * type-check. If $x is a string instead of ?string, null is not
       * subtype of string ...
       *)
      let (env, t_param) = bind_param env (h, param) in
      (env, t_params @ [t_param])
    | None ->
      let ty =
        mk (Reason.Rlambda_param (param.param_pos, get_reason ty), get_node ty)
      in
      let (env, t_param) = bind_param env (ty, param) in
      (env, t_params @ [t_param]))

and closure_bind_variadic env vparam variadic_ty =
  let (env, ty, pos) =
    match hint_of_type_hint vparam.param_type_hint with
    | None ->
      (* if the hint is missing, use the type we expect *)
      (env, variadic_ty, get_pos variadic_ty)
    | Some hint ->
      let pos = fst hint in
      let (env, h) =
        Phase.localize_hint_no_subst env ~ignore_errors:false hint
      in
      let env =
        Typing_coercion.coerce_type
          pos
          Reason.URparam
          env
          variadic_ty
          (MakeType.unenforced h)
          Errors.unify_error
      in
      (env, h, Pos_or_decl.of_raw_pos vparam.param_pos)
  in
  let r = Reason.Rvar_param_from_decl pos in
  let arr_values = mk (r, get_node ty) in
  let ty = MakeType.varray r arr_values in
  let (env, t_variadic) = bind_param env (ty, vparam) in
  (env, t_variadic)

and closure_bind_opt_param env param : env =
  match param.param_expr with
  | None ->
    let ty = Typing_utils.mk_tany env param.param_pos in
    let (env, _) = bind_param env (ty, param) in
    env
  | Some default ->
    let (env, _te, ty) = expr env default ~allow_awaitable:(*?*) false in
    Typing_sequencing.sequence_check_expr default;
    let (env, _) = bind_param env (ty, param) in
    env

(* Make a type-checking function for an anonymous function or lambda. *)
(* Here ret_ty should include Awaitable wrapper *)
(* TODO: ?el is never set; so we need to fix variadic use of lambda *)
and closure_make
    ?el ?ret_ty ?(check_escapes = true) env lambda_pos decl_ft f ft idl is_anon
    =
  let type_closure f =
    (* Wrap the function f so that it can freely clobber function-specific
       parts of the environment; the clobbered parts are restored before
       returning the result. Additionally, we also prevent type parameters
       created in the closure from unsoundly leaking into the environment
       of the enclosing function. *)
    let snap = Typing_escape.snapshot_env env in
    let (env, (escaping, (te, hret, ft))) =
      Env.closure env (fun env ->
          stash_conts_for_closure env lambda_pos is_anon idl (fun env ->
              let (env, res) = f env in
              let escaping = Typing_escape.escaping_from_snapshot snap env in
              (env, (escaping, res))))
    in
    (* After the body of the function is checked, erase all the type parameters
       created from the env and the return type. *)
    let (env, hret) =
      if check_escapes then
        Typing_escape.refresh_env_and_type
          ~remove:escaping
          ~pos:lambda_pos
          env
          hret
      else
        (env, hret)
    in
    (env, (te, hret, ft))
  in
  type_closure @@ fun env ->
  let nb = f.f_body in
  (* Extract capabilities from AAST and add them to the environment *)
  let (env, capability) =
    match (f.f_ctxs, f.f_unsafe_ctxs) with
    | (None, None) ->
      (* if the closure has no explicit coeffect annotations,
         do _not_ insert (unsafe) capabilities into the environment;
         instead, rely on the fact that a capability from an enclosing
         scope can simply be captured, which has the same semantics
         as redeclaring and shadowing with another same-typed capability.
         This avoid unnecessary overhead in the most common case, i.e.,
         when a closure does not need a different (usually smaller)
         set of capabilities. *)
      (env, Env.get_local env Typing_coeffects.local_capability_id)
    | (_, _) ->
      let (env, cap_ty, unsafe_cap_ty) =
        Typing_coeffects.type_capability
          env
          f.f_ctxs
          f.f_unsafe_ctxs
          (fst f.f_name)
      in
      let (env, _) =
        Typing_coeffects.register_capabilities env cap_ty unsafe_cap_ty
      in
      (env, cap_ty)
  in
  let ft = { ft with ft_implicit_params = { capability = CapTy capability } } in
  let env = Env.clear_params env in
  let make_variadic_arg env varg tyl =
    let remaining_types =
      (* It's possible the variadic arg will capture the variadic
       * parameter of the supplied arity (if arity is Fvariadic)
       * and additional supplied params.
       *
       * For example in cases such as:
       *  lambda1 = (int $a, string...$c) ==> {};
       *  lambda1(1, "hello", ...$y); (where $y is a variadic string)
       *  lambda1(1, "hello", "world");
       * then ...$c will contain "hello" and everything in $y in the first
       * example, and "hello" and "world" in the second example.
       *
       * To account for a mismatch in arity, we take the remaining supplied
       * parameters and return a list of all their types. We'll use this
       * to create a union type when creating the typed variadic arg.
       *)
      let remaining_params = List.drop ft.ft_params (List.length f.f_params) in
      List.map ~f:(fun param -> param.fp_type.et_type) remaining_params
    in
    let r = Reason.Rvar_param varg.param_pos in
    let union = Tunion (tyl @ remaining_types) in
    let (env, t_param) = closure_bind_variadic env varg (mk (r, union)) in
    (env, Aast.FVvariadicArg t_param)
  in
  let (env, t_variadic) =
    match (f.f_variadic, ft.ft_arity) with
    | (FVvariadicArg arg, Fvariadic variadic) ->
      make_variadic_arg env arg [variadic.fp_type.et_type]
    | (FVvariadicArg arg, Fstandard) -> make_variadic_arg env arg []
    | (_, _) -> (env, Aast.FVnonVariadic)
  in
  let params = ref f.f_params in
  let (env, t_params) =
    List.fold_left
      ~f:(closure_bind_param params)
      ~init:(env, [])
      (List.map ft.ft_params ~f:(fun x -> x.fp_type.et_type))
  in
  (* Check attributes on the lambda *)
  let (env, user_attributes) =
    attributes_check_def env SN.AttributeKinds.lambda f.f_user_attributes
  in
  let support_dynamic_type =
    Naming_attributes.mem SN.UserAttributes.uaSupportDynamicType user_attributes
    || Env.get_support_dynamic_type env
  in
  let env = List.fold_left ~f:closure_bind_opt_param ~init:env !params in
  let env = List.fold_left ~f:closure_check_param ~init:env f.f_params in
  let env =
    match el with
    | None -> env
    | Some x ->
      let rec iter l1 l2 =
        match (l1, l2) with
        | (_, []) -> ()
        | ([], _) -> ()
        | (x1 :: rl1, (pkx_2, x2) :: rl2) ->
          param_modes x1 x2 pkx_2;
          iter rl1 rl2
      in
      iter ft.ft_params x;
      wfold_left2 inout_write_back env ft.ft_params x
  in
  let env = Env.set_fn_kind env f.f_fun_kind in
  let decl_ty =
    Option.map ~f:(Decl_hint.hint env.decl_env) (hint_of_type_hint f.f_ret)
  in
  let ret_pos =
    match snd f.f_ret with
    | Some (ret_pos, _) -> ret_pos
    | None -> lambda_pos
  in
  let (env, hret) =
    match decl_ty with
    | None ->
      (* Do we have a contextual return type? *)
      begin
        match ret_ty with
        | None -> Typing_return.make_fresh_return_type env ret_pos
        | Some ret_ty -> (env, ret_ty)
      end
    | Some ret ->
      (* If a 'this' type appears it needs to be compatible with the
       * late static type
       *)
      let ety_env =
        empty_expand_env_with_on_error
          (Env.invalid_type_hint_assert_primary_pos_in_current_decl env)
      in
      Typing_return.make_return_type (Phase.localize ~ety_env) env ret
  in
  let (env, hret) =
    Typing_return.force_return_kind ~is_toplevel:false env ret_pos hret
  in
  let ft =
    {
      ft with
      ft_ret = { ft.ft_ret with et_type = hret };
      ft_flags =
        Typing_defs_flags.set_bit
          Typing_defs_flags.ft_flags_support_dynamic_type
          support_dynamic_type
          ft.ft_flags;
    }
  in
  let env =
    Env.set_return
      env
      (Typing_return.make_info
         ret_pos
         f.f_fun_kind
         []
         env
         ~is_explicit:(Option.is_some ret_ty)
         hret
         decl_ty)
  in
  let local_tpenv = Env.get_tpenv env in
  (* Outer pipe variables aren't available in closures. Note that
   * locals are restored by Env.closure after processing the closure
   *)
  let env =
    Env.unset_local env (Local_id.make_unscoped SN.SpecialIdents.dollardollar)
  in
  let (env, tb) = block env nb.fb_ast in
  let has_implicit_return = LEnv.has_next env in
  let env =
    if not has_implicit_return then
      env
    else
      Typing_return.fun_implicit_return env lambda_pos hret f.f_fun_kind
  in
  let has_readonly = Env.get_readonly env in
  let env =
    Typing_env.set_fun_tast_info env Tast.{ has_implicit_return; has_readonly }
  in
  let (env, tparams) = List.map_env env f.f_tparams ~f:type_param in
  let sound_dynamic_check_saved_env = env in
  let params_decl_ty =
    List.map decl_ft.ft_params ~f:(fun { fp_type = { et_type; _ }; _ } ->
        match get_node et_type with
        | Tany _ -> None
        | _ -> Some et_type)
  in
  let variadicity_decl_ty =
    match decl_ft.ft_arity with
    | Fvariadic { fp_type = { et_type; _ }; _ } ->
      begin
        match get_node et_type with
        | Tany _ -> None
        | _ -> Some et_type
      end
    | _ -> None
  in
  if
    TypecheckerOptions.enable_sound_dynamic
      (Provider_context.get_tcopt (Env.get_ctx env))
    && support_dynamic_type
  then
    function_dynamically_callable
      sound_dynamic_check_saved_env
      f
      params_decl_ty
      variadicity_decl_ty
      hret;
  let tfun_ =
    {
      Aast.f_annotation = Env.save local_tpenv env;
      Aast.f_readonly_this = f.f_readonly_this;
      Aast.f_span = f.f_span;
      Aast.f_ret = (hret, hint_of_type_hint f.f_ret);
      Aast.f_readonly_ret = f.f_readonly_ret;
      Aast.f_name = f.f_name;
      Aast.f_tparams = tparams;
      Aast.f_where_constraints = f.f_where_constraints;
      Aast.f_fun_kind = f.f_fun_kind;
      Aast.f_user_attributes = user_attributes;
      Aast.f_body = { Aast.fb_ast = tb };
      Aast.f_ctxs = f.f_ctxs;
      Aast.f_unsafe_ctxs = f.f_unsafe_ctxs;
      Aast.f_params = t_params;
      Aast.f_variadic = t_variadic;
      (* TODO TAST: Variadic efuns *)
      Aast.f_external = f.f_external;
      Aast.f_doc_comment = f.f_doc_comment;
    }
  in
  let ty = mk (Reason.Rwitness lambda_pos, Tfun ft) in
  let te =
    Tast.make_typed_expr
      lambda_pos
      ty
      (if is_anon then
        Aast.Efun (tfun_, idl)
      else
        Aast.Lfun (tfun_, idl))
  in
  let env = Env.set_tyvar_variance env ty in
  (env, (te, hret, ft))

(*****************************************************************************)
(* End of anonymous functions & lambdas. *)
(*****************************************************************************)

(*****************************************************************************)
(* Expression trees *)
(*****************************************************************************)
and expression_tree env p et =
  let {
    et_hint;
    et_splices;
    et_function_pointers;
    et_virtualized_expr;
    et_runtime_expr;
    et_dollardollar_pos;
  } =
    et
  in

  (* Slight hack to deal with |> $$ support *)
  let env =
    match et_dollardollar_pos with
    | Some dd_pos ->
      let dollardollar_var =
        Local_id.make_unscoped SN.ExpressionTrees.dollardollarTmpVar
      in
      let dd_var = Local_id.make_unscoped SN.SpecialIdents.dollardollar in
      let dd_defined = Env.is_local_defined env dd_var in
      if not dd_defined then
        let () = Errors.undefined dd_pos SN.SpecialIdents.dollardollar None in
        let nothing_ty = MakeType.nothing Reason.Rnone in
        Env.set_local env dollardollar_var nothing_ty Pos.none
      else
        let (dd_ty, dd_pos) = Env.get_local_pos env dd_var in
        Env.set_local env dollardollar_var dd_ty dd_pos
    | None -> env
  in

  (* Given the expression tree literal:

     MyVisitor`1 + ${ foo() }`

     First, type check the expressions that are spliced in, so foo() in
     this example. *)
  let (env, t_splices) = block env et_splices in

  (* Next, typecheck the function pointer assignments *)
  let (env, _, t_function_pointers) =
    Typing_env.with_in_expr_tree env true (fun env ->
        let (env, t_function_pointers) = block env et_function_pointers in
        (env, (), t_function_pointers))
  in

  (* Type check the virtualized expression, which will look
     roughly like this:

     function() {
       $0splice0 = foo();
       return MyVisitor::intType()->__plus($0splice0);
     }
  *)
  let (env, t_virtualized_expr, ty_virtual) =
    Typing_env.with_in_expr_tree env true (fun env ->
        expr env et_virtualized_expr ~allow_awaitable:false)
  in

  (* Given the runtime expression:

      MyVisitor::makeTree(...)

      add the inferred type as a type parameter:

      MyVisitor::makeTree<MyVisitorInt>(...)

     and then typecheck. *)
  let (env, runtime_expr) =
    maketree_with_type_param env p et_runtime_expr ty_virtual
  in
  let (env, t_runtime_expr, ty_runtime_expr) =
    expr env runtime_expr ~allow_awaitable:false
  in

  make_result
    env
    p
    (Aast.ExpressionTree
       {
         et_hint;
         et_splices = t_splices;
         et_function_pointers = t_function_pointers;
         et_virtualized_expr = t_virtualized_expr;
         et_runtime_expr = t_runtime_expr;
         et_dollardollar_pos;
       })
    ty_runtime_expr

and et_splice env p e =
  let (env, te, ty) = expr env e ~allow_awaitable:(*?*) false in
  let (env, ty_visitor) = Env.fresh_type env p in
  let (env, ty_res) = Env.fresh_type env p in
  let (env, ty_infer) = Env.fresh_type env p in
  let spliceable_type =
    MakeType.spliceable (Reason.Rsplice p) ty_visitor ty_res ty_infer
  in
  let env = SubType.sub_type env ty spliceable_type (Errors.unify_error_at p) in
  make_result env p (Aast.ET_Splice te) ty_infer

(*****************************************************************************)
(* End expression trees *)
(*****************************************************************************)
and new_object
    ~(expected : ExpectedTy.t option)
    ~check_parent
    ~check_not_abstract
    ~is_using_clause
    p
    env
    cid
    explicit_targs
    el
    unpacked_element =
  (* Obtain class info from the cid expression. We get multiple
   * results with a CIexpr that has a union type, e.g. in

      $classname = (mycond()? classname<A>: classname<B>);
      new $classname();
   *)
  let (env, tal, tcid, classes) =
    instantiable_cid ~exact:Exact p env cid explicit_targs
  in
  let allow_abstract_bound_generic =
    match tcid with
    | (ty, _, Aast.CI (_, tn)) -> is_generic_equal_to tn ty
    | _ -> false
  in
  let gather
      (env, _tel, _typed_unpack_element, should_forget_fakes_acc)
      (cname, class_info, c_ty) =
    if
      check_not_abstract
      && Cls.abstract class_info
      && (not (requires_consistent_construct cid))
      && not allow_abstract_bound_generic
    then
      uninstantiable_error
        env
        p
        cid
        (Cls.pos class_info)
        (Cls.name class_info)
        p
        c_ty;
    let (env, obj_ty_, params) =
      let (env, c_ty) = Env.expand_type env c_ty in
      match (cid, tal, get_class_type c_ty) with
      (* Explicit type arguments *)
      | (CI _, _ :: _, Some (_, _, tyl)) -> (env, get_node c_ty, tyl)
      | (_, _, class_type_opt) ->
        let (env, params) =
          List.map_env env (Cls.tparams class_info) ~f:(fun env tparam ->
              let (env, tvar) =
                Env.fresh_type_reason
                  env
                  p
                  (Reason.Rtype_variable_generics
                     (p, snd tparam.tp_name, strip_ns (snd cname)))
              in
              Typing_log.log_new_tvar_for_new_object env p tvar cname tparam;
              (env, tvar))
        in
        begin
          match class_type_opt with
          | Some (_, Exact, _) -> (env, Tclass (cname, Exact, params), params)
          | _ -> (env, Tclass (cname, Nonexact, params), params)
        end
    in
    if
      (not check_parent)
      && (not is_using_clause)
      && Typing_disposable.is_disposable_class env class_info
    then
      Errors.invalid_new_disposable p;
    let r_witness = Reason.Rwitness p in
    let obj_ty = mk (r_witness, obj_ty_) in
    let c_ty =
      match cid with
      | CIstatic
      | CIexpr _ ->
        mk (r_witness, get_node c_ty)
      | _ -> obj_ty
    in
    let (env, new_ty) =
      let (cid_ty, _, _) = tcid in
      let (env, cid_ty) = Env.expand_type env cid_ty in
      if is_generic cid_ty then
        (env, cid_ty)
      else if check_parent then
        (env, c_ty)
      else
        ExprDepTy.make env ~cid c_ty
    in
    (* Set variance according to type of `new` expression now. Lambda arguments
     * to the constructor might depend on it, and `call_construct` only uses
     * `ctor_fty` to set the variance which has void return type *)
    let env = Env.set_tyvar_variance env new_ty in
    let (env, tel, typed_unpack_element, ctor_fty, should_forget_fakes) =
      let env = check_expected_ty "New" env new_ty expected in
      call_construct p env class_info params el unpacked_element cid new_ty
    in
    let should_forget_fakes_acc =
      should_forget_fakes_acc || should_forget_fakes
    in
    (if equal_consistent_kind (snd (Cls.construct class_info)) Inconsistent then
      match cid with
      | CIstatic -> Errors.new_inconsistent_construct p cname `static
      | CIexpr _ -> Errors.new_inconsistent_construct p cname `classname
      | _ -> ());
    match cid with
    | CIparent ->
      let (env, ctor_fty) =
        match fst (Cls.construct class_info) with
        | Some ({ ce_type = (lazy ty); _ } as ce) ->
          let ety_env =
            {
              empty_expand_env with
              substs =
                TUtils.make_locl_subst_for_class_tparams class_info params;
              this_ty = obj_ty;
            }
          in
          if get_ce_abstract ce then
            Errors.parent_abstract_call
              SN.Members.__construct
              p
              (get_pos ctor_fty);
          let (env, ctor_fty) = Phase.localize ~ety_env env ty in
          (env, ctor_fty)
        | None -> (env, ctor_fty)
      in
      ( (env, tel, typed_unpack_element, should_forget_fakes_acc),
        (obj_ty, ctor_fty) )
    | CIstatic
    | CI _
    | CIself ->
      ( (env, tel, typed_unpack_element, should_forget_fakes_acc),
        (c_ty, ctor_fty) )
    | CIexpr _ ->
      (* When constructing from a (classname) variable, the variable
       * dictates what the constructed object is going to be. This allows
       * for generic and dependent types to be correctly carried
       * through the 'new $foo()' iff the constructed obj_ty is a
       * supertype of the variable-dictated c_ty *)
      let env =
        Typing_ops.sub_type p Reason.URnone env c_ty obj_ty Errors.unify_error
      in
      ( (env, tel, typed_unpack_element, should_forget_fakes_acc),
        (c_ty, ctor_fty) )
  in
  let (had_dynamic, classes) =
    List.fold classes ~init:(false, []) ~f:(fun (seen_dynamic, classes) -> function
      | `Dynamic -> (true, classes)
      | `Class (cname, class_info, c_ty) ->
        (seen_dynamic, (cname, class_info, c_ty) :: classes))
  in
  let ( (env, tel, typed_unpack_element, should_forget_fakes),
        class_types_and_ctor_types ) =
    List.fold_map classes ~init:(env, [], None, false) ~f:gather
  in
  let class_types_and_ctor_types =
    let r = Reason.Rdynamic_construct p in
    let dyn = (mk (r, Tdynamic), mk (r, Tdynamic)) in
    if had_dynamic then
      dyn :: class_types_and_ctor_types
    else
      class_types_and_ctor_types
  in
  let (env, tel, typed_unpack_element, ty, ctor_fty) =
    match class_types_and_ctor_types with
    | [] ->
      let (env, tel, _) =
        argument_list_exprs (expr ~allow_awaitable:false) env el
      in
      let (env, typed_unpack_element, _) =
        match unpacked_element with
        | None -> (env, None, MakeType.nothing Reason.Rnone)
        | Some unpacked_element ->
          let (env, e, ty) =
            expr env unpacked_element ~allow_awaitable:(*?*) false
          in
          (env, Some e, ty)
      in
      let r = Reason.Runknown_class p in
      let terr = TUtils.terr env r in
      (env, tel, typed_unpack_element, terr, terr)
    | [(ty, ctor_fty)] -> (env, tel, typed_unpack_element, ty, ctor_fty)
    | l ->
      let (tyl, ctyl) = List.unzip l in
      let r = Reason.Rwitness p in
      (env, tel, typed_unpack_element, mk (r, Tunion tyl), mk (r, Tunion ctyl))
  in
  let (env, new_ty) =
    let (cid_ty, _, _) = tcid in
    let (env, cid_ty) = Env.expand_type env cid_ty in
    if is_generic cid_ty then
      (env, cid_ty)
    else if check_parent then
      (env, ty)
    else
      ExprDepTy.make env ~cid ty
  in
  ( env,
    tcid,
    tal,
    tel,
    typed_unpack_element,
    new_ty,
    ctor_fty,
    should_forget_fakes )

and attributes_check_def env kind attrs =
  let new_object attr_pos env attr_cid params =
    let (env, _, _, _, _, _, _, _) =
      new_object
        ~expected:None
        ~check_parent:false
        ~check_not_abstract:false
        ~is_using_clause:false
        attr_pos
        env
        (Aast.CI (Positioned.unsafe_to_raw_positioned attr_cid))
        []
        (List.map ~f:(fun e -> (Ast_defs.Pnormal, e)) params)
        (* list of attr parameter literals *)
        None
      (* no variadic arguments *)
    in
    env
  in
  let env = Typing_attributes.check_def env new_object kind attrs in
  List.map_env env attrs ~f:user_attribute

(** Get class infos for a class expression (e.g. `parent`, `self` or
    regular classnames) - which might resolve to a union or intersection
    of classes - and check they are instantiable.

    FIXME: we need to separate our instantiability into two parts. Currently,
    all this function is doing is checking if a given type is inhabited --
    that is, whether there are runtime values of type Aast. However,
    instantiability should be the stricter notion that T has a runtime
    constructor; that is, `new T()` should be valid. In particular, interfaces
    are inhabited, but not instantiable.
    To make this work with classname, we likely need to add something like
    concrete_classname<T>, where T cannot be an interface. *)
and instantiable_cid ?(exact = Nonexact) p env cid explicit_targs :
    newable_class_info =
  let (env, tal, te, classes) =
    class_id_for_new ~exact p env cid explicit_targs
  in
  List.iter classes ~f:(function
      | `Dynamic -> ()
      | `Class ((pos, name), class_info, c_ty) ->
        let pos = Pos_or_decl.unsafe_to_raw_pos pos in
        let kind = Cls.kind class_info in
        if
          Ast_defs.is_c_trait kind
          || Ast_defs.is_c_enum kind
          || Ast_defs.is_c_enum_class kind
        then
          match cid with
          | CIexpr _
          | CI _ ->
            uninstantiable_error env p cid (Cls.pos class_info) name pos c_ty
          | CIstatic
          | CIparent
          | CIself ->
            ()
        else if Ast_defs.is_c_abstract kind && Cls.final class_info then
          uninstantiable_error env p cid (Cls.pos class_info) name pos c_ty
        else
          ());
  (env, tal, te, classes)

(* Deal with assignment of a value of type ty2 to lvalue e1 *)
and assign p env e1 pos2 ty2 =
  assign_with_subtype_err_ p Reason.URassign env e1 pos2 ty2

and assign_ p ur env e1 pos2 ty2 =
  let (env, te, ty, _err) = assign_with_subtype_err_ p ur env e1 pos2 ty2 in
  (env, te, ty)

and assign_with_subtype_err_ p ur env (e1 : Nast.expr) pos2 ty2 =
  match e1 with
  | (_, _, Hole (e, _, _, _)) -> assign_with_subtype_err_ p ur env e pos2 ty2
  | _ ->
    let allow_awaitable = (*?*) false in
    let env =
      match e1 with
      | (_, _, Lvar (_, x)) ->
        Env.forget_prefixed_members env x Reason.(Blame (p, BSassignment))
      (* If we ever extend fake members from $x->a to more complicated lvalues
         such as $x->a->b, we would need to call forget_prefixed_members on
         other lvalues as well. *)
      | (_, _, Obj_get (_, (_, _, Id (_, property)), _, _)) ->
        Env.forget_suffixed_members
          env
          property
          Reason.(Blame (p, BSassignment))
      | _ -> env
    in
    (match e1 with
    | (_, _, Lvar ((_, x) as id)) ->
      let env = set_valid_rvalue p env x ty2 in
      let (_, p1, _) = e1 in
      let (env, te, ty) = make_result env p1 (Aast.Lvar id) ty2 in
      (env, te, ty, None)
    | (_, _, Lplaceholder id) ->
      let placeholder_ty = MakeType.void (Reason.Rplaceholder p) in
      let (_, p1, _) = e1 in
      let (env, te, ty) =
        make_result env p1 (Aast.Lplaceholder id) placeholder_ty
      in
      (env, te, ty, None)
    | (_, _, List el) ->
      (* Generate fresh types for each lhs list element, then subtype against
         rhs type *)
      let (env, tyl) =
        List.map_env env el ~f:(fun env (_, p, _e) -> Env.fresh_type env p)
      in
      let (_, p1, _) = e1 in
      let destructure_ty =
        MakeType.list_destructure (Reason.Rdestructure p1) tyl
      in
      let lty2 = LoclType ty2 in
      let assign_accumulate (env, tel, errs) (lvalue : Nast.expr) ty2 =
        let (env, te, _, err_opt) = assign p env lvalue pos2 ty2 in
        (env, te :: tel, err_opt :: errs)
      in
      let type_list_elem env =
        let (env, reversed_tel, rev_subtype_errs) =
          List.fold2_exn el tyl ~init:(env, [], []) ~f:assign_accumulate
        in
        let (_, p1, _) = e1 in
        let (env, te, ty) =
          make_result env p1 (Aast.List (List.rev reversed_tel)) ty2
        in
        (env, te, ty, List.rev rev_subtype_errs)
      in

      (* Here we attempt to unify the type of the rhs we assigning with
         a number of fresh tyvars corresponding to the arity of the lhs `list`

         if we have a failure in subtyping the fresh tyvars in `destructure_ty`,
         we have a rhs which cannot be destructured to the variables on the lhs;
         e.g. `list($x,$y) = 2`  or `list($x,$y) = tuple (1,2,3)`

         in the error case, we add a `Hole` with expected type `nothing` since
         there is no type we can suggest was expected

         in the ok case were the destrucutring succeeded, the fresh vars
         now have types so we can subtype each element, accumulate the errors
         and pack back into the rhs structure as our expected type *)
      Result.fold
        ~ok:(fun env ->
          let (env, te, ty, subty_errs) = type_list_elem env in
          let err_opt =
            if List.for_all subty_errs ~f:Option.is_none then
              None
            else
              Some (ty2, pack_errs pos2 ty2 (subty_errs, None))
          in
          (env, te, ty, err_opt))
        ~error:(fun env ->
          let (env, te, ty, _) = type_list_elem env in
          let nothing =
            MakeType.nothing @@ Reason.Rsolve_fail (Pos_or_decl.of_raw_pos pos2)
          in
          (env, te, ty, Some (ty2, nothing)))
      @@ Result.map ~f:(fun env -> Env.set_tyvar_variance_i env destructure_ty)
      @@ Type.sub_type_i_res p ur env lty2 destructure_ty Errors.unify_error
    | ( _,
        pobj,
        Obj_get
          (obj, (_, pm, Id ((_, member_name) as m)), nullflavor, prop_or_method)
      ) ->
      let lenv = env.lenv in
      let nullsafe =
        match nullflavor with
        | OG_nullthrows -> None
        | OG_nullsafe -> Some (Reason.Rnullsafe_op pobj)
      in
      let (env, tobj, obj_ty) =
        expr ~accept_using_var:true env obj ~allow_awaitable
      in
      let env = might_throw env in
      let (_, p1, _) = obj in
      let (env, (declared_ty, _tal), lval_err_opt, rval_err_opt) =
        TOG.obj_get_with_err
          ~obj_pos:p1
          ~is_method:false
          ~nullsafe
          ~inst_meth:false
          ~meth_caller:false
          ~coerce_from_ty:(Some (p, ur, ty2))
          ~explicit_targs:[]
          ~class_id:(CIexpr e1)
          ~member_id:m
          ~on_error:Errors.unify_error
          env
          obj_ty
      in
      let te1 =
        Tast.make_typed_expr
          pobj
          declared_ty
          (Aast.Obj_get
             ( hole_on_err ~err_opt:lval_err_opt tobj,
               Tast.make_typed_expr pm declared_ty (Aast.Id m),
               nullflavor,
               prop_or_method ))
      in
      let env = { env with lenv } in
      begin
        match obj with
        | (_, _, This)
        | (_, _, Lvar _) ->
          let (env, local) = Env.FakeMembers.make env obj member_name p in
          let (env, refined_ty) =
            Inter.intersect env ~r:(Reason.Rwitness p) declared_ty ty2
          in
          let env = set_valid_rvalue p env local refined_ty in
          (env, te1, ty2, rval_err_opt)
        | _ -> (env, te1, ty2, rval_err_opt)
      end
    | (_, _, Obj_get _) ->
      let lenv = env.lenv in
      let no_fakes = LEnv.env_with_empty_fakes env in
      let (env, te1, real_type) = lvalue no_fakes e1 in
      let (env, exp_real_type) = Env.expand_type env real_type in
      let env = { env with lenv } in
      let (env, err_opt) =
        Result.fold
          ~ok:(fun env -> (env, None))
          ~error:(fun env -> (env, Some (ty2, exp_real_type)))
        @@ Typing_coercion.coerce_type_res
             p
             ur
             env
             ty2
             (MakeType.unenforced exp_real_type)
             Errors.unify_error
      in
      (env, te1, ty2, err_opt)
    | (_, _, Class_get (_, CGexpr _, _)) ->
      failwith "AST should not have any CGexprs after naming"
    | (_, _, Class_get (((_, _, x) as cid), CGstring (pos_member, y), _)) ->
      let lenv = env.lenv in
      let no_fakes = LEnv.env_with_empty_fakes env in
      let (env, te1, _) = lvalue no_fakes e1 in
      let env = { env with lenv } in
      let (env, ety2) = Env.expand_type env ty2 in
      (* This defers the coercion check to class_get, which looks up the appropriate target type *)
      let (env, _tal, _, cty) = class_expr env [] cid in
      let env = might_throw env in
      let (env, (declared_ty, _), rval_err_opt) =
        class_get_err
          ~is_method:false
          ~is_const:false
          ~coerce_from_ty:(Some (p, ur, ety2))
          env
          cty
          (pos_member, y)
          cid
      in
      let (env, local) = Env.FakeMembers.make_static env x y p in
      let (env, refined_ty) =
        Inter.intersect env ~r:(Reason.Rwitness p) declared_ty ty2
      in
      let env = set_valid_rvalue p env local refined_ty in
      (env, te1, ty2, rval_err_opt)
    | (_, pos, Array_get (e1, None)) ->
      let (env, te1, ty1) = update_array_type pos env e1 `lvalue in
      let (_, p1, _) = e1 in
      let (env, ty1', arr_err_opt, val_err_opt) =
        Typing_array_access.assign_array_append_with_err
          ~array_pos:p1
          ~expr_pos:p
          ur
          env
          ty1
          ty2
      in
      let (env, te1) =
        if is_hack_collection env ty1 then
          (env, hole_on_err ~err_opt:arr_err_opt te1)
        else
          let (env, te1, ty, _) =
            assign_with_subtype_err_ p ur env e1 p1 ty1'
          in
          (* Update the actual type to that after assignment *)
          let arr_err_opt =
            Option.map arr_err_opt ~f:(fun (_, ty_expect) -> (ty, ty_expect))
          in
          (env, hole_on_err ~err_opt:arr_err_opt te1)
      in
      let (env, te, ty) =
        make_result env pos (Aast.Array_get (te1, None)) ty2
      in
      (env, te, ty, val_err_opt)
    | (_, pos, Array_get (e1, Some e)) ->
      let (env, te1, ty1) = update_array_type pos env e1 `lvalue in
      let (env, te, ty) = expr env e ~allow_awaitable in
      let env = might_throw env in
      let (_, p1, _) = e1 in
      let (env, ty1', arr_err_opt, key_err_opt, val_err_opt) =
        Typing_array_access.assign_array_get_with_err
          ~array_pos:p1
          ~expr_pos:p
          ur
          env
          ty1
          e
          ty
          ty2
      in
      let (env, te1) =
        if is_hack_collection env ty1 then
          (env, hole_on_err ~err_opt:arr_err_opt te1)
        else
          let (env, te1, ty, _) =
            assign_with_subtype_err_ p ur env e1 p1 ty1'
          in
          (* Update the actual type to that after assignment *)
          let arr_err_opt =
            Option.map arr_err_opt ~f:(fun (_, ty_expect) -> (ty, ty_expect))
          in
          (env, hole_on_err ~err_opt:arr_err_opt te1)
      in
      ( env,
        ( ty2,
          pos,
          Aast.Array_get (te1, Some (hole_on_err ~err_opt:key_err_opt te)) ),
        ty2,
        val_err_opt )
    | _ -> assign_simple p ur env e1 ty2)

and assign_simple pos ur env e1 ty2 =
  let (env, te1, ty1) = lvalue env e1 in
  let (env, err_opt) =
    Result.fold
      ~ok:(fun env -> (env, None))
      ~error:(fun env -> (env, Some (ty2, ty1)))
    @@ Typing_coercion.coerce_type_res
         pos
         ur
         env
         ty2
         (MakeType.unenforced ty1)
         Errors.unify_error
  in
  (env, te1, ty2, err_opt)

and array_field env ~allow_awaitable = function
  | AFvalue ve ->
    let (env, tve, tv) = expr env ve ~allow_awaitable in
    (env, (Aast.AFvalue tve, None, tv))
  | AFkvalue (ke, ve) ->
    let (env, tke, tk) = expr env ke ~allow_awaitable in
    let (env, tve, tv) = expr env ve ~allow_awaitable in
    (env, (Aast.AFkvalue (tke, tve), Some tk, tv))

and array_value ~(expected : ExpectedTy.t option) env x =
  let (env, te, ty) = expr ?expected env x ~allow_awaitable:(*?*) false in
  (env, (te, ty))

and arraykey_value
    ?(add_hole = false)
    p
    class_name
    is_set
    ~(expected : ExpectedTy.t option)
    env
    ((_, pos, _) as x) =
  let (env, (te, ty)) = array_value ~expected env x in
  let (ty_arraykey, reason) =
    if is_set then
      ( MakeType.arraykey (Reason.Rset_element pos),
        Reason.set_element class_name )
    else
      (MakeType.arraykey (Reason.Ridx_dict pos), Reason.index_class class_name)
  in
  let ty_expected = { et_type = ty_arraykey; et_enforced = Enforced } in
  let (env, te) =
    if add_hole then
      (* If we have an error in coercion here, we will add a `Hole` indicating the
           actual and expected type. The `Hole` may then be used in a codemod to
           add a call to `UNSAFE_CAST` so we need to consider what type we expect.

           If we were to add an expected type of 'arraykey' here it would be
           correct but adding an `UNSAFE_CAST<?string,arraykey>($x)` means we
           get cascading errors if we have e.g. a return type of keyset<string>.

           To try and prevent this, if this is an optional type where the nonnull
           part can be coerced to arraykey, we prefer that type as our expected type.
      *)
      let (ok, ty_actual) =
        match deref ty with
        | (_, Toption ty_inner) ->
          ( (fun env ->
              let r =
                Reason.to_string "Expected `arraykey`" (Reason.Ridx_dict pos)
                @ [
                    ( get_pos ty,
                      Format.sprintf
                        "But got `?%s`"
                        (Typing_print.full_strip_ns env ty_inner) );
                  ]
              in
              (* We actually failed so generate the error we should
                 have seen *)
              Errors.unify_error (p, Reason.string_of_ureason reason) r;

              (env, Some (ty, ty_inner))),
            ty_inner )
        | _ -> ((fun env -> (env, None)), ty)
      in
      let (env, err_opt) =
        Result.fold ~ok ~error:(fun env -> (env, Some (ty_actual, ty_arraykey)))
        @@ Typing_coercion.coerce_type_res
             ~coerce_for_op:true
             p
             reason
             env
             ty_actual
             ty_expected
             Errors.unify_error
      in
      (env, hole_on_err ~err_opt te)
    else
      let env =
        Typing_coercion.coerce_type
          ~coerce_for_op:true
          p
          reason
          env
          ty
          ty_expected
          Errors.unify_error
      in
      (env, te)
  in

  (env, (te, ty))

and check_parent_construct pos env el unpacked_element env_parent =
  let check_not_abstract = false in
  let (env, env_parent) =
    Phase.localize_no_subst env ~ignore_errors:true env_parent
  in
  let ( env,
        _tcid,
        _tal,
        tel,
        typed_unpack_element,
        parent,
        fty,
        should_forget_fakes ) =
    new_object
      ~expected:None
      ~check_parent:true
      ~check_not_abstract
      ~is_using_clause:false
      pos
      env
      CIparent
      []
      el
      unpacked_element
  in
  (* Not sure why we need to equate these types *)
  let env =
    Type.sub_type pos Reason.URnone env env_parent parent Errors.unify_error
  in
  let env =
    Type.sub_type pos Reason.URnone env parent env_parent Errors.unify_error
  in
  ( env,
    tel,
    typed_unpack_element,
    MakeType.void (Reason.Rwitness pos),
    parent,
    fty,
    should_forget_fakes )

and call_parent_construct pos env el unpacked_element =
  match Env.get_parent_ty env with
  | Some parent -> check_parent_construct pos env el unpacked_element parent
  | None ->
    (* continue here *)
    let ty = Typing_utils.mk_tany env pos in
    let should_invalidate_fake_members = true in
    let default = (env, [], None, ty, ty, ty, should_invalidate_fake_members) in
    (match Env.get_self_id env with
    | Some self ->
      (match Env.get_class env self with
      | Some trait when Ast_defs.is_c_trait (Cls.kind trait) ->
        (match trait_most_concrete_req_class trait env with
        | None ->
          Errors.parent_in_trait pos;
          default
        | Some (c, parent_ty) ->
          (match Cls.construct c with
          | (_, Inconsistent) ->
            Errors.trait_parent_construct_inconsistent pos (Cls.pos c)
          | _ -> ());
          check_parent_construct pos env el unpacked_element parent_ty)
      | Some _self_tc ->
        Errors.undefined_parent pos;
        default
      | None -> assert false)
    | None ->
      Errors.parent_outside_class pos;
      let ty = err_witness env pos in
      (env, [], None, ty, ty, ty, should_invalidate_fake_members))

(* Depending on the kind of expression we are dealing with
 * The typing of call is different.
 *)
and dispatch_call
    ~(expected : ExpectedTy.t option)
    ~is_using_clause
    ?in_await
    p
    env
    ((_, fpos, fun_expr) as e : Nast.expr)
    explicit_targs
    el
    unpacked_element =
  let expr = expr ~allow_awaitable:(*?*) false in
  let make_call env te tal tel typed_unpack_element ty =
    make_result env p (Aast.Call (te, tal, tel, typed_unpack_element)) ty
  in
  (* TODO: Avoid Tany annotations in TAST by eliminating `make_call_special` *)
  let make_call_special env id tel ty =
    make_call
      env
      (Tast.make_typed_expr fpos (TUtils.mk_tany env fpos) (Aast.Id id))
      []
      tel
      None
      ty
  in
  (* For special functions and pseudofunctions with a definition in an HHI
   * file. It is preferred over [make_call_special] because it does not generate
   * [TAny] for the function type of the call.
   *)
  let make_call_special_from_def env id tel ty_ =
    let (env, fty, tal) = fun_type_of_id env id explicit_targs el in
    let ty =
      match get_node fty with
      | Tfun ft -> ft.ft_ret.et_type
      | _ -> ty_ (Reason.Rwitness p)
    in
    make_call env (Tast.make_typed_expr fpos fty (Aast.Id id)) tal tel None ty
  in
  let overload_function = overload_function make_call fpos in
  (* Require [get_idisposable_value()] function calls to be inside a [using]
     statement. *)
  let check_disposable_in_return env fty =
    if is_return_disposable_fun_type env fty && not is_using_clause then
      Errors.invalid_new_disposable p
  in

  let dispatch_id env id =
    let (env, fty, tal) = fun_type_of_id env id explicit_targs el in
    check_disposable_in_return env fty;
    let (env, (tel, typed_unpack_element, ty, should_forget_fakes)) =
      call ~expected p env fty el unpacked_element
    in
    let result =
      make_call
        env
        (Tast.make_typed_expr fpos fty (Aast.Id id))
        tal
        tel
        typed_unpack_element
        ty
    in
    (result, should_forget_fakes)
  in
  let dispatch_class_const env ((_, pos, e1_) as e1) m =
    let (env, _tal, tcid, ty1) = class_expr env [] e1 in
    let this_ty = MakeType.this (Reason.Rwitness fpos) in
    (* In static context, you can only call parent::foo() on static methods.
     * In instance context, you can call parent:foo() on static
     * methods as well as instance methods
     *)
    let is_static =
      (not (Nast.equal_class_id_ e1_ CIparent))
      || Env.is_static env
      || class_contains_smethod env ty1 m
    in
    let (env, (fty, tal)) =
      if not is_static then
        (* parent::nonStaticFunc() is really weird. It's calling a method
         * defined on the parent class, but $this is still the child class.
         *)
        TOG.obj_get
          ~inst_meth:false
          ~meth_caller:false
          ~is_method:true
          ~nullsafe:None
          ~obj_pos:pos
          ~coerce_from_ty:None
          ~explicit_targs:[]
          ~class_id:e1_
          ~member_id:m
          ~on_error:Errors.unify_error
          ~parent_ty:ty1
          env
          this_ty
      else
        class_get
          ~coerce_from_ty:None
          ~is_method:true
          ~is_const:false
          ~explicit_targs
          env
          ty1
          m
          e1
    in
    check_disposable_in_return env fty;
    let (env, (tel, typed_unpack_element, ty, should_forget_fakes)) =
      call ~expected p env fty el unpacked_element
    in
    let result =
      make_call
        env
        (Tast.make_typed_expr fpos fty (Aast.Class_const (tcid, m)))
        tal
        tel
        typed_unpack_element
        ty
    in
    (result, should_forget_fakes)
  in
  match fun_expr with
  (* Special top-level function *)
  | Id ((pos, x) as id) when SN.StdlibFunctions.needs_special_dispatch x ->
    begin
      match x with
      (* Special function [echo]. *)
      | echo when String.equal echo SN.SpecialFunctions.echo ->
        (* TODO(tany): TODO(T92020097):
         * Add [function print(arraykey ...$args)[io]: void] to an HHI file and
         * remove special casing of [echo] and [print].
         *)
        let env = Typing_local_ops.enforce_io pos env in
        let (env, tel, _) =
          argument_list_exprs (expr ~accept_using_var:true) env el
        in
        let arraykey_ty = MakeType.arraykey (Reason.Rwitness pos) in
        let like_ak_ty =
          MakeType.union
            (Reason.Rwitness pos)
            [MakeType.dynamic (Reason.Rwitness pos); arraykey_ty]
        in
        let (env, rev_tel) =
          List.fold
            tel
            ~init:(env, [])
            ~f:(fun (env, tel) (pk, ((ty, pos, _) as te)) ->
              let (env, err_opt) =
                Result.fold
                  ~ok:(fun env -> (env, None))
                  ~error:(fun env -> (env, Some (ty, arraykey_ty)))
                @@ SubType.sub_type_res
                     env
                     ty
                     like_ak_ty
                     (Errors.invalid_echo_argument_at pos)
              in
              (env, (pk, hole_on_err ~err_opt te) :: tel))
        in
        let tel = List.rev rev_tel in
        let should_forget_fakes = false in
        ( make_call_special env id tel (MakeType.void (Reason.Rwitness pos)),
          should_forget_fakes )
      (* `unsafe_cast` *)
      | unsafe_cast when String.equal unsafe_cast SN.PseudoFunctions.unsafe_cast
        ->
        let result =
          match el with
          | [(Ast_defs.Pnormal, original_expr)]
            when TypecheckerOptions.ignore_unsafe_cast (Env.get_tcopt env) ->
            expr env original_expr
          | _ ->
            (* first type the `unsafe_cast` as a call, handling arity errors *)
            let (env, fty, tal) = fun_type_of_id env id explicit_targs el in
            check_disposable_in_return env fty;
            let (env, (tel, _, ty, _should_forget_fakes)) =
              call ~expected p env fty el unpacked_element
            in
            (* construct the `Hole` using default value and type arguments
               if necessary *)
            let dflt_ty = MakeType.err Reason.none in
            let el =
              match tel with
              | (_, e) :: _ -> e
              | [] -> Tast.make_typed_expr fpos dflt_ty Aast.Null
            and (ty_from, ty_to) =
              match tal with
              | (ty_from, _) :: (ty_to, _) :: _ -> (ty_from, ty_to)
              | (ty, _) :: _ -> (ty, ty)
              | _ -> (dflt_ty, dflt_ty)
            in
            let te =
              Aast.Hole
                (el, ty_from, ty_to, UnsafeCast (List.map ~f:snd explicit_targs))
            in
            make_result env p te ty
        in
        let should_forget_fakes = false in
        (result, should_forget_fakes)
      (* Special function `isset` *)
      | isset when String.equal isset SN.PseudoFunctions.isset ->
        let (env, tel, _) =
          argument_list_exprs
            (expr ~accept_using_var:true ~check_defined:false)
            env
            el
        in
        if Option.is_some unpacked_element then
          Errors.unpacking_disallowed_builtin_function p isset;
        let should_forget_fakes = false in
        let result = make_call_special_from_def env id tel MakeType.bool in
        (result, should_forget_fakes)
      (* Special function `unset` *)
      | unset when String.equal unset SN.PseudoFunctions.unset ->
        let (env, tel, _) = argument_list_exprs expr env el in
        if Option.is_some unpacked_element then
          Errors.unpacking_disallowed_builtin_function p unset;
        let env = Typing_local_ops.check_unset_target env tel in
        let checked_unset_error = Errors.unset_nonidx_in_strict in
        let env =
          match (el, unpacked_element) with
          | ( [
                ( Ast_defs.Pnormal,
                  (_, _, Array_get ((_, _, Class_const _), Some _)) );
              ],
              None ) ->
            Errors.const_mutation p Pos_or_decl.none "";
            env
          | ([(Ast_defs.Pnormal, (_, _, Array_get (ea, Some _)))], None) ->
            let (env, _te, ty) = expr env ea in
            let r = Reason.Rwitness p in
            let tmixed = MakeType.mixed r in
            let super =
              mk
                ( Reason.Rnone,
                  Tunion
                    [
                      MakeType.dynamic r;
                      MakeType.dict r tmixed tmixed;
                      MakeType.keyset r tmixed;
                      MakeType.darray r tmixed tmixed;
                    ] )
            in
            SubType.sub_type_or_fail env ty super (fun () ->
                checked_unset_error
                  p
                  (Reason.to_string
                     ("This is "
                     ^ Typing_print.error ~ignore_dynamic:true env ty)
                     (get_reason ty)))
          | _ ->
            checked_unset_error p [];
            env
        in
        let should_forget_fakes = false in
        let result =
          match el with
          | [(_, (_, p, Obj_get (_, _, OG_nullsafe, _)))] ->
            Errors.nullsafe_property_write_context p;
            make_call_special_from_def env id tel (TUtils.terr env)
          | _ -> make_call_special_from_def env id tel MakeType.void
        in
        (result, should_forget_fakes)
      | type_structure
        when String.equal type_structure SN.StdlibFunctions.type_structure
             && Int.equal (List.length el) 2
             && Option.is_none unpacked_element ->
        let should_forget_fakes = false in
        (match el with
        | [e1; e2] ->
          (match e2 with
          | (_, (_, p, String cst)) ->
            (* find the class constant implicitly defined by the typeconst *)
            let cid =
              match e1 with
              | (_, (_, _, Class_const (cid, (_, x))))
              | (_, (_, _, Class_get (cid, CGstring (_, x), _)))
                when String.equal x SN.Members.mClass ->
                cid
              | _ ->
                let (_, ((_, p1, _) as e1_)) = e1 in
                ((), p1, CIexpr e1_)
            in
            let result = class_const ~incl_tc:true env p (cid, (p, cst)) in
            (result, should_forget_fakes)
          | _ ->
            Errors.illegal_type_structure pos "second argument is not a string";
            let result = expr_error env (Reason.Rwitness pos) e in
            (result, should_forget_fakes))
        | _ -> assert false)
      | _ -> dispatch_id env id
    end
  (* Special Shapes:: function *)
  | Class_const (((_, _, CI (_, shapes)) as class_id), ((_, x) as method_id))
    when String.equal shapes SN.Shapes.cShapes ->
    begin
      match x with
      (* Special function `Shapes::idx` *)
      | idx when String.equal idx SN.Shapes.idx ->
        overload_function
          p
          env
          class_id
          method_id
          el
          unpacked_element
          (fun env fty res el ->
            match el with
            | [(_, shape); (_, field)] ->
              let (env, _ts, shape_ty) = expr env shape in
              let (_, shape_pos, _) = shape in
              Typing_shapes.idx
                env
                shape_ty
                field
                None
                ~expr_pos:p
                ~fun_pos:(get_reason fty)
                ~shape_pos
            | [(_, shape); (_, field); (_, default)] ->
              let (env, _ts, shape_ty) = expr env shape in
              let (env, _td, default_ty) = expr env default in
              let (_, shape_pos, _) = shape in
              let (_, default_pos, _) = default in
              Typing_shapes.idx
                env
                shape_ty
                field
                (Some (default_pos, default_ty))
                ~expr_pos:p
                ~fun_pos:(get_reason fty)
                ~shape_pos
            | _ -> (env, res))
      (* Special function `Shapes::at` *)
      | at when String.equal at SN.Shapes.at ->
        overload_function
          p
          env
          class_id
          method_id
          el
          unpacked_element
          (fun env _fty res el ->
            match el with
            | [(_, shape); (_, field)] ->
              let (env, _te, shape_ty) = expr env shape in
              let (_, shape_pos, _) = shape in
              Typing_shapes.at env ~expr_pos:p ~shape_pos shape_ty field
            | _ -> (env, res))
      (* Special function `Shapes::keyExists` *)
      | key_exists when String.equal key_exists SN.Shapes.keyExists ->
        overload_function
          p
          env
          class_id
          method_id
          el
          unpacked_element
          (fun env fty res el ->
            match el with
            | [(_, shape); (_, field)] ->
              let (env, _te, shape_ty) = expr env shape in
              (* try accessing the field, to verify existence, but ignore
                 * the returned type and keep the one coming from function
                 * return type hint *)
              let (_, shape_pos, _) = shape in
              let (env, _) =
                Typing_shapes.idx
                  env
                  shape_ty
                  field
                  None
                  ~expr_pos:p
                  ~fun_pos:(get_reason fty)
                  ~shape_pos
              in
              (env, res)
            | _ -> (env, res))
      (* Special function `Shapes::removeKey` *)
      | remove_key when String.equal remove_key SN.Shapes.removeKey ->
        overload_function
          p
          env
          class_id
          method_id
          el
          unpacked_element
          (fun env _ res el ->
            match el with
            | [(Ast_defs.Pinout _, shape); (_, field)] ->
              begin
                match shape with
                | (_, _, Lvar (_, lvar))
                | (_, _, Hole ((_, _, Lvar (_, lvar)), _, _, _)) ->
                  let (env, _te, shape_ty) = expr env shape in
                  let (env, shape_ty) =
                    Typing_shapes.remove_key p env shape_ty field
                  in
                  let env = set_valid_rvalue p env lvar shape_ty in
                  (env, res)
                | _ ->
                  let (_, shape_pos, _) = shape in
                  Errors.invalid_shape_remove_key shape_pos;
                  (env, res)
              end
            | _ -> (env, res))
      (* Special function `Shapes::toArray` *)
      | to_array when String.equal to_array SN.Shapes.toArray ->
        overload_function
          p
          env
          class_id
          method_id
          el
          unpacked_element
          (fun env _ res el ->
            match el with
            | [(_, shape)] ->
              let (env, _te, shape_ty) = expr env shape in
              Typing_shapes.to_array env p shape_ty res
            | _ -> (env, res))
      (* Special function `Shapes::toDict` *)
      | to_dict when String.equal to_dict SN.Shapes.toDict ->
        overload_function
          p
          env
          class_id
          method_id
          el
          unpacked_element
          (fun env _ res el ->
            match el with
            | [(_, shape)] ->
              let (env, _te, shape_ty) = expr env shape in
              Typing_shapes.to_dict env p shape_ty res
            | _ -> (env, res))
      | _ -> dispatch_class_const env class_id method_id
    end
  (* Special function `parent::__construct` *)
  | Class_const ((_, pos, CIparent), ((_, construct) as id))
    when String.equal construct SN.Members.__construct ->
    let (env, tel, typed_unpack_element, ty, pty, ctor_fty, should_forget_fakes)
        =
      call_parent_construct p env el unpacked_element
    in
    let result =
      make_call
        env
        (Tast.make_typed_expr
           fpos
           ctor_fty
           (Aast.Class_const ((pty, pos, Aast.CIparent), id)))
        [] (* tal: no type arguments to constructor *)
        tel
        typed_unpack_element
        ty
    in
    (result, should_forget_fakes)
  (* Calling parent / class method *)
  | Class_const (class_id, m) -> dispatch_class_const env class_id m
  (* Readonly Expressions do not affect the type, but need to be threaded through when they're part of a call *)
  | ReadonlyExpr r ->
    let env = Env.set_readonly env true in
    (* Recurse onto the inner call *)
    let ((env, expr, ty), s) =
      dispatch_call
        ~expected
        ~is_using_clause
        ?in_await
        p
        env
        r
        explicit_targs
        el
        unpacked_element
    in
    (match expr with
    | (ty, _, Call (caller, tal, tel, c)) ->
      let (caller_ty, caller_pos, _) = caller in
      (* Rewrap the caller in the readonly expression after we're done *)
      let wrapped_caller =
        Tast.make_typed_expr caller_pos caller_ty (Aast.ReadonlyExpr caller)
      in
      let result = make_call env wrapped_caller tal tel c ty in
      (result, s)
    | _ -> ((env, expr, ty), s))
  (* Call instance method *)
  | Obj_get (e1, (_, pos_id, Id m), nullflavor, Is_method)
    when not (TypecheckerOptions.method_call_inference (Env.get_tcopt env)) ->
    let (env, te1, ty1) = expr ~accept_using_var:true env e1 in
    let nullsafe =
      match nullflavor with
      | OG_nullthrows -> None
      | OG_nullsafe -> Some p
    in
    let (_, p1, _) = e1 in
    let (env, (tfty, tal), lval_err_opt, _rval_err_opt) =
      TOG.obj_get_with_err
        ~obj_pos:p1
        ~is_method:true
        ~inst_meth:false
        ~meth_caller:false
        ~nullsafe:(Option.map ~f:(fun p -> Reason.Rnullsafe_op p) nullsafe)
        ~coerce_from_ty:None
        ~explicit_targs
        ~class_id:(CIexpr e1)
        ~member_id:m
        ~on_error:Errors.unify_error
        env
        ty1
    in
    check_disposable_in_return env tfty;
    let (env, (tel, typed_unpack_element, ty, should_forget_fakes)) =
      call ~nullsafe ~expected p env tfty el unpacked_element
    in
    let result =
      make_call
        env
        (Tast.make_typed_expr
           fpos
           tfty
           (Aast.Obj_get
              ( hole_on_err ~err_opt:lval_err_opt te1,
                Tast.make_typed_expr pos_id tfty (Aast.Id m),
                nullflavor,
                Is_method )))
        tal
        tel
        typed_unpack_element
        ty
    in
    (result, should_forget_fakes)
  (* Call instance method using new method call inference *)
  | Obj_get (receiver, (_, pos_id, Id meth), nullflavor, Is_method) ->
    (*****
        Typecheck `Obj_get` by enforcing that:
        - `<instance_type>` <: `Thas_member(m, #1)`
        where #1 is a fresh type variable.
      *****)
    let (env, typed_receiver, receiver_ty) =
      expr ~accept_using_var:true env receiver
    in
    let env = might_throw env in
    let nullsafe =
      match nullflavor with
      | OG_nullthrows -> None
      | OG_nullsafe -> Some p
    in
    (* Generate a fresh type `method_ty` for the type of the
       instance method, i.e. #1 *)
    let (env, method_ty) = Env.fresh_type env p in
    (* Create `Thas_member` constraint type *)
    let (_, receiver_p, _) = receiver in
    let reason = Reason.Rwitness receiver_p in
    let has_method_ty =
      MakeType.has_member
        reason
        ~name:meth
        ~ty:method_ty
        ~class_id:(CIexpr receiver)
        ~explicit_targs:(Some explicit_targs)
    in
    let env = Env.set_tyvar_variance env method_ty in
    let (env, has_method_super_ty) =
      if Option.is_none nullsafe then
        (env, has_method_ty)
      else
        (* If null-safe, then `receiver_ty` <: `?Thas_member(m, #1)`,
           but *unlike* property access typing in `expr_`, we still use `#1` as
           our "result" if `receiver_ty` is nullable  (as opposed to `?#1`),
           deferring null-safety handling to after `call` *)
        let r = Reason.Rnullsafe_op p in
        let null_ty = MakeType.null r in
        Union.union_i env r has_method_ty null_ty
    in
    let (_, receiver_pos, _) = receiver in
    let env_res =
      Type.sub_type_i_res
        receiver_pos
        Reason.URnone
        env
        (LoclType receiver_ty)
        has_method_super_ty
        Errors.unify_error
    in
    let ty_nothing = MakeType.nothing Reason.none in
    let (env, err_opt) =
      Result.fold
        env_res
        ~ok:(fun env -> (env, None))
        ~error:(fun env -> (env, Some (receiver_ty, ty_nothing)))
    in

    (* Perhaps solve for `method_ty`. Opening and closing a scope is too coarse
       here - type parameters are localised to fresh type variables over the
       course of subtyping above, and we do not want to solve these until later.
       Once we typecheck all function calls with a subtyping of function types,
       we should not need to solve early at all - transitive closure of
       subtyping should give enough information. *)
    let env =
      match get_var method_ty with
      | Some var ->
        Typing_solver.solve_to_equal_bound_or_wrt_variance env Reason.Rnone var
      | None -> env
    in
    let localize_targ env (_, targ) = Phase.localize_targ env targ in
    let (env, typed_targs) =
      List.map_env env ~f:(localize_targ ~check_well_kinded:true) explicit_targs
    in
    check_disposable_in_return env method_ty;
    let (env, (typed_params, typed_unpack_element, ret_ty, should_forget_fakes))
        =
      call ~nullsafe ~expected ?in_await p env method_ty el unpacked_element
    in
    (* If the call is nullsafe AND the receiver is nullable,
       make the return type nullable too *)
    let (env, ret_ty) =
      if Option.is_some nullsafe then
        let r = Reason.Rnullsafe_op p in
        let null_ty = MakeType.null r in
        let (env, null_or_nothing_ty) =
          Inter.intersect env ~r null_ty receiver_ty
        in
        let (env, ret_option_ty) = Union.union env null_or_nothing_ty ret_ty in
        (env, ret_option_ty)
      else
        (env, ret_ty)
    in
    let result =
      make_call
        env
        (Tast.make_typed_expr
           fpos
           method_ty
           (Aast.Obj_get
              ( hole_on_err ~err_opt typed_receiver,
                Tast.make_typed_expr pos_id method_ty (Aast.Id meth),
                nullflavor,
                Is_method )))
        typed_targs
        typed_params
        typed_unpack_element
        ret_ty
    in
    (result, should_forget_fakes)
  (* Function invocation *)
  | Fun_id x ->
    let (env, fty, tal) = fun_type_of_id env x explicit_targs el in
    check_disposable_in_return env fty;
    let (env, (tel, typed_unpack_element, ty, should_forget_fakes)) =
      call ~expected p env fty el unpacked_element
    in
    let result =
      make_call
        env
        (Tast.make_typed_expr fpos fty (Aast.Fun_id x))
        tal
        tel
        typed_unpack_element
        ty
    in
    (result, should_forget_fakes)
  | Id id -> dispatch_id env id
  | _ ->
    let (env, te, fty) = expr env e in
    let (env, fty) =
      Typing_solver.expand_type_and_solve
        ~description_of_expected:"a function value"
        env
        fpos
        fty
    in
    check_disposable_in_return env fty;
    let (env, (tel, typed_unpack_element, ty, should_forget_fakes)) =
      call ~expected p env fty el unpacked_element
    in
    let result =
      make_call
        env
        te
        (* tal: no type arguments to function values, as they are non-generic *)
        []
        tel
        typed_unpack_element
        ty
    in
    (result, should_forget_fakes)

and class_get_res
    ~is_method
    ~is_const
    ~coerce_from_ty
    ?(explicit_targs = [])
    ?(incl_tc = false)
    ?(is_function_pointer = false)
    env
    cty
    (p, mid)
    cid =
  let (env, this_ty) =
    if is_method then
      this_for_method env cid cty
    else
      (env, cty)
  in
  class_get_inner
    ~is_method
    ~is_const
    ~this_ty
    ~explicit_targs
    ~incl_tc
    ~coerce_from_ty
    ~is_function_pointer
    env
    cid
    cty
    (p, mid)

and class_get_err
    ~is_method
    ~is_const
    ~coerce_from_ty
    ?explicit_targs
    ?incl_tc
    ?is_function_pointer
    env
    cty
    (p, mid)
    cid =
  let (env, tys, rval_res_opt) =
    class_get_res
      ~is_method
      ~is_const
      ~coerce_from_ty
      ?explicit_targs
      ?incl_tc
      ?is_function_pointer
      env
      cty
      (p, mid)
      cid
  in
  let rval_err_opt = Option.bind ~f:Result.error rval_res_opt in
  (env, tys, rval_err_opt)

and class_get
    ~is_method
    ~is_const
    ~coerce_from_ty
    ?explicit_targs
    ?incl_tc
    ?is_function_pointer
    env
    cty
    (p, mid)
    cid =
  let (env, tys, _) =
    class_get_res
      ~is_method
      ~is_const
      ~coerce_from_ty
      ?explicit_targs
      ?incl_tc
      ?is_function_pointer
      env
      cty
      (p, mid)
      cid
  in
  (env, tys)

and class_get_inner
    ~is_method
    ~is_const
    ~this_ty
    ~coerce_from_ty
    ?(explicit_targs = [])
    ?(incl_tc = false)
    ?(is_function_pointer = false)
    env
    ((_, cid_pos, cid_) as cid)
    cty
    (p, mid) =
  let (env, cty) = Env.expand_type env cty in
  let dflt_rval_err = Option.map ~f:(fun (_, _, ty) -> Ok ty) coerce_from_ty in
  match deref cty with
  | (r, Tany _) -> (env, (mk (r, Typing_utils.tany env), []), dflt_rval_err)
  | (_, Terr) -> (env, (err_witness env cid_pos, []), dflt_rval_err)
  | (_, Tdynamic) -> (env, (cty, []), dflt_rval_err)
  | (_, Tunion tyl) ->
    let (env, pairs, rval_err_opts) =
      List.fold_left
        tyl
        ~init:(env, [], [])
        ~f:(fun (env, pairs, rval_err_opts) ty ->
          let (env, pair, rval_err_opt) =
            class_get_res
              ~is_method
              ~is_const
              ~explicit_targs
              ~incl_tc
              ~coerce_from_ty
              ~is_function_pointer
              env
              ty
              (p, mid)
              cid
          in
          (env, pair :: pairs, rval_err_opt :: rval_err_opts))
    in

    let rval_err = Option.(map ~f:union_coercion_errs @@ all rval_err_opts) in
    let (env, ty) =
      Union.union_list env (get_reason cty) (List.map ~f:fst pairs)
    in
    (env, (ty, []), rval_err)
  | (_, Tintersection tyl) ->
    let (env, pairs, rval_err_opts) =
      TUtils.run_on_intersection_res env tyl ~f:(fun env ty ->
          class_get_inner
            ~is_method
            ~is_const
            ~this_ty
            ~explicit_targs
            ~incl_tc
            ~coerce_from_ty
            ~is_function_pointer
            env
            cid
            ty
            (p, mid))
    in
    let rval_err =
      Option.(map ~f:intersect_coercion_errs @@ all rval_err_opts)
    in
    let (env, ty) =
      Inter.intersect_list env (get_reason cty) (List.map ~f:fst pairs)
    in
    (env, (ty, []), rval_err)
  | (_, Tnewtype (_, _, ty))
  | (_, Tdependent (_, ty)) ->
    class_get_inner
      ~is_method
      ~is_const
      ~this_ty
      ~explicit_targs
      ~incl_tc
      ~coerce_from_ty
      ~is_function_pointer
      env
      cid
      ty
      (p, mid)
  | (r, Tgeneric _) ->
    let (env, tyl) =
      TUtils.get_concrete_supertypes ~abstract_enum:true env cty
    in
    if List.is_empty tyl then begin
      Errors.non_class_member
        ~is_method
        mid
        p
        (Typing_print.error env cty)
        (get_pos cty);
      (env, (err_witness env p, []), dflt_rval_err)
    end else
      let (env, ty) = Typing_intersection.intersect_list env r tyl in
      class_get_inner
        ~is_method
        ~is_const
        ~this_ty
        ~explicit_targs
        ~incl_tc
        ~coerce_from_ty
        ~is_function_pointer
        env
        cid
        ty
        (p, mid)
  | (_, Tclass ((_, c), _, paraml)) ->
    let class_ = Env.get_class env c in
    (match class_ with
    | None -> (env, (Typing_utils.mk_tany env p, []), dflt_rval_err)
    | Some class_ ->
      (* TODO akenn: Should we move this to the class_get original call? *)
      let (env, this_ty) = ExprDepTy.make env ~cid:cid_ this_ty in
      (* We need to instantiate generic parameters in the method signature *)
      let ety_env =
        {
          empty_expand_env with
          this_ty;
          substs = TUtils.make_locl_subst_for_class_tparams class_ paraml;
        }
      in
      let get_smember_from_constraints env class_info =
        let upper_bounds =
          Cls.upper_bounds_on_this_from_constraints class_info
        in
        let (env, upper_bounds) =
          List.map_env env upper_bounds ~f:(fun env up ->
              Phase.localize ~ety_env env up)
        in
        let (env, inter_ty) =
          Inter.intersect_list env (Reason.Rwitness p) upper_bounds
        in
        class_get_inner
          ~is_method
          ~is_const
          ~this_ty
          ~explicit_targs
          ~incl_tc
          ~coerce_from_ty
          ~is_function_pointer
          env
          cid
          inter_ty
          (p, mid)
      in
      let try_get_smember_from_constraints env class_info =
        Errors.try_with_error
          (fun () -> get_smember_from_constraints env class_info)
          (fun () ->
            TOG.smember_not_found
              p
              ~is_const
              ~is_method
              ~is_function_pointer
              class_info
              mid
              Errors.unify_error;
            (env, (TUtils.terr env Reason.Rnone, []), dflt_rval_err))
      in
      if is_const then (
        let const =
          if incl_tc then
            Env.get_const env class_ mid
          else
            match Env.get_typeconst env class_ mid with
            | Some _ ->
              Errors.illegal_typeconst_direct_access p;
              None
            | None -> Env.get_const env class_ mid
        in
        match const with
        | None when Cls.has_upper_bounds_on_this_from_constraints class_ ->
          try_get_smember_from_constraints env class_
        | None ->
          TOG.smember_not_found
            p
            ~is_const
            ~is_method
            ~is_function_pointer
            class_
            mid
            Errors.unify_error;
          (env, (TUtils.terr env Reason.Rnone, []), dflt_rval_err)
        | Some { cc_type; cc_abstract; cc_pos; _ } ->
          let (env, cc_locl_type) = Phase.localize ~ety_env env cc_type in
          (match cc_abstract with
          | CCAbstract _ ->
            (match cid_ with
            | CIstatic
            | CIexpr _ ->
              ()
            | _ ->
              let cc_name = Cls.name class_ ^ "::" ^ mid in
              Errors.abstract_const_usage p cc_pos cc_name)
          | CCConcrete -> ());
          (env, (cc_locl_type, []), dflt_rval_err)
      ) else
        let static_member_opt =
          Env.get_static_member is_method env class_ mid
        in
        (match static_member_opt with
        | None when Cls.has_upper_bounds_on_this_from_constraints class_ ->
          try_get_smember_from_constraints env class_
        | None ->
          TOG.smember_not_found
            p
            ~is_const
            ~is_method
            ~is_function_pointer
            class_
            mid
            Errors.unify_error;
          (env, (TUtils.terr env Reason.Rnone, []), dflt_rval_err)
        | Some
            ({
               ce_visibility = vis;
               ce_type = (lazy member_decl_ty);
               ce_deprecated;
               _;
             } as ce) ->
          let def_pos = get_pos member_decl_ty in
          TVis.check_class_access
            ~use_pos:p
            ~def_pos
            env
            (vis, get_ce_lsb ce)
            cid_
            class_;
          TVis.check_deprecated ~use_pos:p ~def_pos ce_deprecated;
          check_class_get env p def_pos c mid ce cid is_function_pointer;
          let (env, member_ty, et_enforced, tal) =
            match deref member_decl_ty with
            (* We special case Tfun here to allow passing in explicit tparams to localize_ft. *)
            | (r, Tfun ft) when is_method ->
              let (env, explicit_targs) =
                Phase.localize_targs
                  ~check_well_kinded:true
                  ~is_method:true
                  ~def_pos
                  ~use_pos:p
                  ~use_name:(strip_ns mid)
                  env
                  ft.ft_tparams
                  (List.map ~f:snd explicit_targs)
              in
              let ft =
                Typing_enforceability.compute_enforced_and_pessimize_fun_type
                  env
                  ft
              in
              let (env, ft) =
                Phase.(
                  localize_ft
                    ~instantiation:
                      { use_name = strip_ns mid; use_pos = p; explicit_targs }
                    ~ety_env
                    ~def_pos
                    env
                    ft)
              in
              let fty =
                Typing_dynamic.relax_method_type
                  env
                  (get_ce_support_dynamic_type ce)
                  r
                  ft
              in
              (env, fty, Unenforced, explicit_targs)
            (* unused *)
            | _ ->
              let { et_type; et_enforced } =
                Typing_enforceability.compute_enforced_and_pessimize_ty
                  env
                  member_decl_ty
              in
              let (env, member_ty) = Phase.localize ~ety_env env et_type in
              (* TODO(T52753871) make function just return possibly_enforced_ty
               * after considering intersection case *)
              (env, member_ty, et_enforced, [])
          in
          let (env, member_ty) =
            if Cls.has_upper_bounds_on_this_from_constraints class_ then
              let ((env, (member_ty', _), _), succeed) =
                Errors.try_
                  (fun () -> (get_smember_from_constraints env class_, true))
                  (fun _ ->
                    (* No eligible functions found in constraints *)
                    ( (env, (MakeType.mixed Reason.Rnone, []), dflt_rval_err),
                      false ))
              in
              if succeed then
                Inter.intersect env ~r:(Reason.Rwitness p) member_ty member_ty'
              else
                (env, member_ty)
            else
              (env, member_ty)
          in
          let (env, rval_err) =
            match coerce_from_ty with
            | None -> (env, None)
            | Some (p, ur, ty) ->
              Result.fold
                ~ok:(fun env -> (env, Some (Ok ty)))
                ~error:(fun env -> (env, Some (Error (ty, member_ty))))
              @@ Typing_coercion.coerce_type_res
                   p
                   ur
                   env
                   ty
                   { et_type = member_ty; et_enforced }
                   Errors.unify_error
          in
          (env, (member_ty, tal), rval_err)))
  | (_, Tunapplied_alias _) ->
    Typing_defs.error_Tunapplied_alias_in_illegal_context ()
  | ( _,
      ( Tvar _ | Tnonnull | Tvarray _ | Tdarray _ | Tvarray_or_darray _
      | Tvec_or_dict _ | Toption _ | Tprim _ | Tfun _ | Ttuple _ | Tshape _
      | Taccess _ | Tneg _ ) ) ->
    Errors.non_class_member
      ~is_method
      mid
      p
      (Typing_print.error env cty)
      (get_pos cty);
    (env, (err_witness env p, []), dflt_rval_err)

and class_id_for_new
    ~exact p env (cid : Nast.class_id_) (explicit_targs : Nast.targ list) :
    newable_class_info =
  let (env, tal, te, cid_ty) =
    class_expr
      ~check_targs_well_kinded:true
      ~check_explicit_targs:true
      ~exact
      env
      explicit_targs
      ((), p, cid)
  in
  (* Need to deal with union case *)
  let rec get_info res tyl =
    match tyl with
    | [] -> (env, tal, te, res)
    | ty :: tyl ->
      (match get_node ty with
      | Tunion tyl'
      | Tintersection tyl' ->
        get_info res (tyl' @ tyl)
      | _ ->
        (* Instantiation on an abstract class (e.g. from classname<T>) is
         * via the base type (to check constructor args), but the actual
         * type `ty` must be preserved. *)
        (match get_node (TUtils.get_base_type env ty) with
        | Tdynamic -> get_info (`Dynamic :: res) tyl
        | Tclass (sid, _, _) ->
          let class_ = Env.get_class env (snd sid) in
          (match class_ with
          | None -> get_info res tyl
          | Some class_info ->
            (match (te, cid_ty) with
            (* When computing the classes for a new T() where T is a generic,
             * the class must be consistent (final, final constructor, or
             * <<__ConsistentConstruct>>) for its constructor to be considered *)
            | ((_, _, Aast.CI (_, c)), ty) when is_generic_equal_to c ty ->
              (* Only have this choosing behavior for new T(), not all generic types
               * i.e. new classname<T>, TODO: T41190512 *)
              if Cls.valid_newable_class class_info then
                get_info (`Class (sid, class_info, ty) :: res) tyl
              else
                get_info res tyl
            | _ -> get_info (`Class (sid, class_info, ty) :: res) tyl))
        | _ -> get_info res tyl))
  in
  get_info [] [cid_ty]

(* When invoking a method, the class_id is used to determine what class we
 * lookup the method in, but the type of 'this' will be the late bound type.
 * For example:
 *
 *  class C {
 *    public static function get(): this { return new static(); }
 *
 *    public static function alias(): this { return self::get(); }
 *  }
 *
 *  In C::alias, when we invoke self::get(), 'self' is resolved to the class
 *  in the lexical scope (C), so call C::get. However the method is executed in
 *  the current context, so static inside C::get will be resolved to the late
 *  bound type (get_called_class() within C::alias).
 *
 *  This means when determining the type of this, CIparent and CIself should be
 *  changed to CIstatic. For the other cases of C::get() or $c::get(), we only
 *  look at the left hand side of the '::' and use the type associated
 *  with it.
 *
 *  Thus C::get() will return a type C, while $c::get() will return the same
 *  type as $c.
 *)
and this_for_method env (_, p, cid) default_ty =
  match cid with
  | CIparent
  | CIself
  | CIstatic ->
    let (env, _tal, _te, ty) = class_expr env [] ((), p, CIstatic) in
    ExprDepTy.make env ~cid:CIstatic ty
  | _ -> (env, default_ty)

(** Resolve class expressions:
 *     self    CIself       lexically enclosing class
 *     parent  CIparent     lexically enclosing `extends` class
 *     static  CIstatic     late-static-bound class (i.e. runtime receiver)
 *     <id>    CI id        literal class name
 *     <expr>  CIexpr expr  expression that evaluates to an object or classname
 *)
and class_expr
    ?(check_targs_well_kinded = false)
    ?(exact = Nonexact)
    ?(check_explicit_targs = false)
    (env : env)
    (tal : Nast.targ list)
    ((_, p, cid_) : Nast.class_id) :
    env * Tast.targ list * Tast.class_id * locl_ty =
  let make_result env tal te ty = (env, tal, (ty, p, te), ty) in
  match cid_ with
  | CIparent ->
    (match Env.get_self_id env with
    | Some self ->
      (match Env.get_class env self with
      | Some trait when Ast_defs.is_c_trait (Cls.kind trait) ->
        (match trait_most_concrete_req_class trait env with
        | None ->
          Errors.parent_in_trait p;
          make_result env [] Aast.CIparent (err_witness env p)
        | Some (_, parent_ty) ->
          (* inside a trait, parent is SN.Typehints.this, but with the
           * type of the most concrete class that the trait has
           * "require extend"-ed *)
          let (env, parent_ty) =
            Phase.localize_no_subst env ~ignore_errors:true parent_ty
          in
          make_result env [] Aast.CIparent parent_ty)
      | _ ->
        let parent =
          match Env.get_parent_ty env with
          | None ->
            Errors.parent_undefined p;
            mk (Reason.none, Typing_defs.make_tany ())
          | Some parent -> parent
        in
        let (env, parent) =
          Phase.localize_no_subst env ~ignore_errors:true parent
        in
        (* parent is still technically the same object. *)
        make_result env [] Aast.CIparent parent)
    | None ->
      let parent =
        match Env.get_parent_ty env with
        | None ->
          Errors.parent_undefined p;
          mk (Reason.none, Typing_defs.make_tany ())
        | Some parent -> parent
      in
      let (env, parent) =
        Phase.localize_no_subst env ~ignore_errors:true parent
      in
      (* parent is still technically the same object. *)
      make_result env [] Aast.CIparent parent)
  | CIstatic ->
    let this =
      if Option.is_some (Env.next_cont_opt env) then
        MakeType.this (Reason.Rwitness p)
      else
        MakeType.nothing (Reason.Rwitness p)
    in
    make_result env [] Aast.CIstatic this
  | CIself ->
    let ty =
      match Env.get_self_class_type env with
      | Some (c, _, tyl) -> mk (Reason.Rwitness p, Tclass (c, exact, tyl))
      | None ->
        (* Naming phase has already checked and replaced CIself with CI if outside a class *)
        Errors.internal_error p "Unexpected CIself";
        Typing_utils.mk_tany env p
    in
    make_result env [] Aast.CIself ty
  | CI ((p, id) as c) ->
    begin
      match Env.get_pos_and_kind_of_generic env id with
      | Some (def_pos, kind) ->
        let simple_kind = Typing_kinding_defs.Simple.from_full_kind kind in
        let param_nkinds =
          Typing_kinding_defs.Simple.get_named_parameter_kinds simple_kind
        in
        let (env, tal) =
          Phase.localize_targs_with_kinds
            ~check_well_kinded:check_targs_well_kinded
            ~is_method:true
            ~def_pos
            ~use_pos:p
            ~use_name:(strip_ns (snd c))
            ~check_explicit_targs
            env
            param_nkinds
            (List.map ~f:snd tal)
        in
        let r = Reason.Rhint (Pos_or_decl.of_raw_pos p) in
        let type_args = List.map tal ~f:fst in
        let tgeneric = MakeType.generic ~type_args r id in
        make_result env tal (Aast.CI c) tgeneric
      | None ->
        (* Not a type parameter *)
        let class_ = Env.get_class env id in
        (match class_ with
        | None -> make_result env [] (Aast.CI c) (Typing_utils.mk_tany env p)
        | Some class_ ->
          TVis.check_classname_access ~use_pos:p ~in_signature:false env class_;
          let (env, ty, tal) =
            List.map ~f:snd tal
            |> Phase.localize_targs_and_check_constraints
                 ~exact
                 ~check_well_kinded:check_targs_well_kinded
                 ~def_pos:(Cls.pos class_)
                 ~use_pos:p
                 ~check_explicit_targs
                 env
                 c
                 (Cls.tparams class_)
          in
          make_result env tal (Aast.CI c) ty)
    end
  | CIexpr ((_, p, _) as e) ->
    let (env, te, ty) = expr env e ~allow_awaitable:(*?*) false in
    let fold_errs errs =
      let rec aux = function
        | (Ok xs, Ok x :: rest) -> aux (Ok (x :: xs), rest)
        | (Ok xs, Error (x, y) :: rest) -> aux (Error (x :: xs, y :: xs), rest)
        | (Error (xs, ys), Ok x :: rest) -> aux (Error (x :: xs, x :: ys), rest)
        | (Error (xs, ys), Error (x, y) :: rest) ->
          aux (Error (x :: xs, y :: ys), rest)
        | (acc, []) -> acc
      in
      aux (Ok [], errs)
    in
    let rec resolve_ety env ty =
      let (env, ty) =
        Typing_solver.expand_type_and_solve
          ~description_of_expected:"an object"
          env
          p
          ty
      in
      let base_ty = TUtils.get_base_type env ty in
      match deref base_ty with
      | (_, Tnewtype (classname, [the_cls], as_ty))
        when String.equal classname SN.Classes.cClassname ->
        let (env, ty, err_res) = resolve_ety env the_cls in
        let wrap ty = mk (Reason.none, Tnewtype (classname, [ty], as_ty)) in
        let err_res =
          match err_res with
          | Ok ty -> Ok (wrap ty)
          | Error (ty_act, ty_exp) -> Error (wrap ty_act, wrap ty_exp)
        in
        (env, ty, err_res)
      | (_, Tgeneric _)
      | (_, Tclass _) ->
        (env, ty, Ok ty)
      | (r, Tunion tyl) ->
        let (env, tyl, errs) = List.map_env_err_res env tyl ~f:resolve_ety in
        let ty = MakeType.union r tyl in
        let err =
          match fold_errs errs with
          | Ok _ -> Ok ty
          | Error (ty_actuals, ty_expects) ->
            let ty_actual = MakeType.union Reason.none ty_actuals
            and ty_expect = MakeType.union Reason.none ty_expects in
            Error (ty_actual, ty_expect)
        in
        (env, ty, err)
      | (r, Tintersection tyl) ->
        let (env, tyl, errs) =
          TUtils.run_on_intersection_res env tyl ~f:resolve_ety
        in
        let (env, ty) = Inter.intersect_list env r tyl in
        let (env, err) =
          match fold_errs errs with
          | Ok _ -> (env, Ok ty)
          | Error (ty_actuals, ty_expects) ->
            let (env, ty_actual) =
              Inter.intersect_list env Reason.none ty_actuals
            in
            let (env, ty_expect) =
              Inter.intersect_list env Reason.none ty_expects
            in
            (env, Error (ty_actual, ty_expect))
        in
        (env, ty, err)
      | (_, Tdynamic) -> (env, base_ty, Ok base_ty)
      | (_, Terr) ->
        let ty = err_witness env p in
        (env, ty, Ok ty)
      | (r, Tvar _) ->
        Errors.unknown_type "an object" p (Reason.to_string "It is unknown" r);
        let ty = err_witness env p in
        (env, ty, Ok ty)
      | (_, Tunapplied_alias _) ->
        Typing_defs.error_Tunapplied_alias_in_illegal_context ()
      | ( _,
          ( Tany _ | Tnonnull | Tvarray _ | Tdarray _ | Tvarray_or_darray _
          | Tvec_or_dict _ | Toption _ | Tprim _ | Tfun _ | Ttuple _
          | Tnewtype _ | Tdependent _ | Tshape _ | Taccess _ | Tneg _ ) ) ->
        Errors.expected_class
          ~suffix:(", but got " ^ Typing_print.error env base_ty)
          p;
        let ty_nothing = MakeType.nothing Reason.none in
        let ty_expect = MakeType.classname Reason.none [ty_nothing] in
        (env, err_witness env p, Error (base_ty, ty_expect))
    in
    let (env, result_ty, err_res) = resolve_ety env ty in
    let err_opt =
      Result.fold
        err_res
        ~ok:(fun _ -> None)
        ~error:(fun (ty_act, ty_expect) -> Some (ty_act, ty_expect))
    in
    let te = hole_on_err ~err_opt te in
    let x = make_result env [] (Aast.CIexpr te) result_ty in
    x

and call_construct p env class_ params el unpacked_element cid cid_ty =
  let cid_ty =
    if Nast.equal_class_id_ cid CIparent then
      MakeType.this (Reason.Rwitness p)
    else
      cid_ty
  in
  let ety_env =
    {
      empty_expand_env with
      this_ty = cid_ty;
      substs = TUtils.make_locl_subst_for_class_tparams class_ params;
      on_error = Errors.unify_error_at p;
    }
  in
  let env =
    Phase.check_where_constraints
      ~in_class:true
      ~use_pos:p
      ~definition_pos:(Cls.pos class_)
      ~ety_env
      env
      (Cls.where_constraints class_)
  in
  let cstr = Env.get_construct env class_ in
  match fst cstr with
  | None ->
    if (not (List.is_empty el)) || Option.is_some unpacked_element then
      Errors.constructor_no_args p;
    let (env, tel, _tyl) =
      argument_list_exprs (expr ~allow_awaitable:false) env el
    in
    let should_forget_fakes = true in
    (env, tel, None, TUtils.terr env Reason.Rnone, should_forget_fakes)
  | Some { ce_visibility = vis; ce_type = (lazy m); ce_deprecated; _ } ->
    let def_pos = get_pos m in
    TVis.check_obj_access ~use_pos:p ~def_pos env vis;
    TVis.check_deprecated ~use_pos:p ~def_pos ce_deprecated;
    (* Obtain the type of the constructor *)
    let (env, m) =
      let r = get_reason m |> Typing_reason.localize in
      match get_node m with
      | Tfun ft ->
        let ft =
          Typing_enforceability.compute_enforced_and_pessimize_fun_type env ft
        in
        (* This creates type variables for non-denotable type parameters on constructors.
         * These are notably different from the tparams on the class, which are handled
         * at the top of this function. User-written type parameters on constructors
         * are still a parse error. This is a no-op if ft.ft_tparams is empty. *)
        let (env, implicit_constructor_targs) =
          Phase.localize_targs
            ~check_well_kinded:true
            ~is_method:true
            ~def_pos
            ~use_pos:p
            ~use_name:"constructor"
            env
            ft.ft_tparams
            []
        in
        let (env, ft) =
          Phase.(
            localize_ft
              ~instantiation:
                {
                  use_name = "constructor";
                  use_pos = p;
                  explicit_targs = implicit_constructor_targs;
                }
              ~ety_env
              ~def_pos
              env
              ft)
        in
        (env, mk (r, Tfun ft))
      | _ ->
        Errors.internal_error p "Expected function type for constructor";
        let ty = TUtils.terr env r in
        (env, ty)
    in
    let (env, (tel, typed_unpack_element, _ty, should_forget_fakes)) =
      call ~expected:None p env m el unpacked_element
    in
    (env, tel, typed_unpack_element, m, should_forget_fakes)

and inout_write_back env { fp_type; _ } (pk, ((_, pos, _) as e)) =
  match pk with
  | Ast_defs.Pinout _ ->
    (* Translate the write-back semantics of inout parameters.
     *
     * This matters because we want to:
     * (1) make sure we can write to the original argument
     *     (modifiable lvalue check)
     * (2) allow for growing of locals / Tunions (type side effect)
     *     but otherwise unify the argument type with the parameter hint
     *)
    let (env, _te, _ty) =
      assign_ pos Reason.URparam_inout env e pos fp_type.et_type
    in
    env
  | _ -> env

(** Typechecks a call.
 * Returns in this order the typed expressions for the arguments, for the
 * variadic arguments, the return type, and a boolean indicating whether fake
 * members should be forgotten.
 *)
and call
    ~(expected : ExpectedTy.t option)
    ?(nullsafe : Pos.t option = None)
    ?in_await
    pos
    env
    fty
    (el : (Ast_defs.param_kind * Nast.expr) list)
    (unpacked_element : Nast.expr option) :
    env
    * ((Ast_defs.param_kind * Tast.expr) list
      * Tast.expr option
      * locl_ty
      * bool) =
  let expr = expr ~allow_awaitable:(*?*) false in
  let (env, tyl) = TUtils.get_concrete_supertypes ~abstract_enum:true env fty in
  if List.is_empty tyl then begin
    bad_call env pos fty;
    let env = call_untyped_unpack env (get_pos fty) unpacked_element in
    let should_forget_fakes = true in
    (env, ([], None, err_witness env pos, should_forget_fakes))
  end else
    let (env, fty) =
      Typing_intersection.intersect_list env (get_reason fty) tyl
    in
    let (env, efty) =
      if TypecheckerOptions.method_call_inference (Env.get_tcopt env) then
        Env.expand_type env fty
      else
        Typing_solver.expand_type_and_solve
          ~description_of_expected:"a function value"
          env
          pos
          fty
    in
    match deref efty with
    | (r, Tdynamic) when TCO.enable_sound_dynamic (Env.get_tcopt env) ->
      let ty = MakeType.dynamic (Reason.Rdynamic_call pos) in
      let el =
        (* Need to check that the type of the unpacked_element can be,
         * coerced to dynamic, just like all of the other arguments, in addition
         * to the check below in call_untyped_unpack, that it is unpackable.
         * We don't need to unpack and check each type because a tuple is
         * coercible iff it's constituent types are. *)
        Option.value_map
          ~f:(fun u -> el @ [(Ast_defs.Pnormal, u)])
          ~default:el
          unpacked_element
      in
      let (env, tel) =
        List.map_env env el ~f:(fun env (pk, elt) ->
            (* TODO(sowens): Pass the expected type to expr *)
            let (env, te, e_ty) = expr env elt in
            let env =
              match pk with
              | Ast_defs.Pinout _ ->
                let (_, pos, _) = elt in
                let (env, _te, _ty) =
                  assign_ pos Reason.URparam_inout env elt pos efty
                in
                env
              | Ast_defs.Pnormal -> env
            in
            let (env, err_opt) =
              Result.fold
                ~ok:(fun env -> (env, None))
                ~error:(fun env -> (env, Some (e_ty, ty)))
              @@ Typing_subtype.sub_type_res
                   ~coerce:(Some Typing_logic.CoerceToDynamic)
                   env
                   e_ty
                   ty
                   (Errors.unify_error_at pos)
            in
            (env, (pk, hole_on_err ~err_opt te)))
      in
      let env = call_untyped_unpack env (Reason.to_pos r) unpacked_element in
      let should_forget_fakes = true in
      (env, (tel, None, ty, should_forget_fakes))
    | (r, ((Tprim Tnull | Tdynamic | Terr | Tany _ | Tunion []) as ty))
      when match ty with
           | Tprim Tnull -> Option.is_some nullsafe
           | _ -> true ->
      let el =
        Option.value_map
          ~f:(fun u -> el @ [(Ast_defs.Pnormal, u)])
          ~default:el
          unpacked_element
      in
      let expected_arg_ty =
        (* Note: We ought to be using 'mixed' here *)
        ExpectedTy.make pos Reason.URparam (Typing_utils.mk_tany env pos)
      in
      let (env, tel) =
        List.map_env env el ~f:(fun env (pk, elt) ->
            let (env, te, ty) = expr ~expected:expected_arg_ty env elt in
            let (env, err_opt) =
              if TCO.global_inference (Env.get_tcopt env) then
                match get_node efty with
                | Terr
                | Tany _
                | Tdynamic ->
                  Result.fold
                    ~ok:(fun env -> (env, None))
                    ~error:(fun env -> (env, Some (ty, efty)))
                  @@ Typing_coercion.coerce_type_res
                       pos
                       Reason.URparam
                       env
                       ty
                       (MakeType.unenforced efty)
                       Errors.unify_error
                | _ -> (env, None)
              else
                (env, None)
            in
            let env =
              match pk with
              | Ast_defs.Pinout _ ->
                let (_, pos, _) = elt in
                let (env, _te, _ty) =
                  assign_ pos Reason.URparam_inout env elt pos efty
                in
                env
              | Ast_defs.Pnormal -> env
            in
            (env, (pk, hole_on_err ~err_opt te)))
      in
      let env = call_untyped_unpack env (Reason.to_pos r) unpacked_element in
      let ty =
        match ty with
        | Tprim Tnull -> mk (r, Tprim Tnull)
        | Tdynamic -> MakeType.dynamic (Reason.Rdynamic_call pos)
        | Terr
        | Tany _ ->
          Typing_utils.mk_tany env pos
        | Tunion []
        | _ (* _ should not happen! *) ->
          mk (r, Tunion [])
      in
      let should_forget_fakes = true in
      (env, (tel, None, ty, should_forget_fakes))
    | (_, Tunion [ty]) ->
      call ~expected ~nullsafe ?in_await pos env ty el unpacked_element
    | (r, Tunion tyl) ->
      let (env, resl) =
        List.map_env env tyl ~f:(fun env ty ->
            call ~expected ~nullsafe ?in_await pos env ty el unpacked_element)
      in
      let should_forget_fakes =
        List.exists resl ~f:(fun (_, _, _, forget) -> forget)
      in
      let retl = List.map resl ~f:(fun (_, _, x, _) -> x) in
      let (env, ty) = Union.union_list env r retl in
      (* We shouldn't be picking arbitrarily for the TAST here, as TAST checks
       * depend on the types inferred. Here's we're preserving legacy behaviour
       * by picking the last one.
       * TODO: don't do this, instead use subtyping to push unions
       * through function types
       *)
      let (tel, typed_unpack_element, _, _) = List.hd_exn (List.rev resl) in
      (env, (tel, typed_unpack_element, ty, should_forget_fakes))
    | (r, Tintersection tyl) ->
      let (env, resl) =
        TUtils.run_on_intersection env tyl ~f:(fun env ty ->
            call ~expected ~nullsafe ?in_await pos env ty el unpacked_element)
      in
      let should_forget_fakes =
        List.for_all resl ~f:(fun (_, _, _, forget) -> forget)
      in
      let retl = List.map resl ~f:(fun (_, _, x, _) -> x) in
      let (env, ty) = Inter.intersect_list env r retl in
      (* We shouldn't be picking arbitrarily for the TAST here, as TAST checks
       * depend on the types inferred. Here we're preserving legacy behaviour
       * by picking the last one.
       * TODO: don't do this, instead use subtyping to push intersections
       * through function types
       *)
      let (tel, typed_unpack_element, _, _) = List.hd_exn (List.rev resl) in
      (env, (tel, typed_unpack_element, ty, should_forget_fakes))
    | (r2, Tfun ft) ->
      (* Typing of format string functions. It is dependent on the arguments (el)
       * so it cannot be done earlier.
       *)
      let pos_def = Reason.to_pos r2 in
      let (env, ft) = Typing_exts.retype_magic_func env ft el in
      let (env, var_param) = variadic_param env ft in
      (* Force subtype with expected result *)
      let env =
        check_expected_ty "Call result" env ft.ft_ret.et_type expected
      in
      let env = Env.set_tyvar_variance env ft.ft_ret.et_type in
      let is_lambda (_, _, e) =
        match e with
        | Efun _
        | Lfun _ ->
          true
        | _ -> false
      in
      let get_next_param_info paraml =
        match paraml with
        | param :: paraml -> (false, Some param, paraml)
        | [] -> (true, var_param, paraml)
      in
      let rec compute_enum_name env lty =
        match get_node lty with
        | Tclass ((_, enum_name), _, _) when Env.is_enum_class env enum_name ->
          (env, Some enum_name)
        | Tgeneric (name, _) ->
          let (env, upper_bounds) =
            Typing_utils.collect_enum_class_upper_bounds env name
          in
          begin
            match upper_bounds with
            | None -> (env, None)
            | Some upper_bound ->
              (* To avoid ambiguity, we only support the case where
               * there is a single upper bound that is an EnumClass.
               * We might want to relax that later (e.g. with  the
               * support for intersections.
               * See Typing_type_wellformedness.check_via_label_on_param.
               *)
              begin
                match get_node upper_bound with
                | Tclass ((_, name), _, _) when Env.is_enum_class env name ->
                  (env, Some name)
                | _ -> (env, None)
              end
          end
        | Tvar var ->
          (* minimal support to only deal with Tvar when:
           * - it is the valueOf from BuiltinEnumClass.
           *   In this case, we know the tvar as a single lowerbound,
           *   `this` which must be an enum class.
           * - it is a generic "TX::T" from a where clause. We try
           *   to look at an upper bound, if it is an enum class, or fail.
           *
           * We could relax this in the future but
           * I want to avoid complex constraints for now.
           *)
          let lower_bounds = Env.get_tyvar_lower_bounds env var in
          if ITySet.cardinal lower_bounds <> 1 then
            (env, None)
          else (
            match ITySet.choose lower_bounds with
            | ConstraintType _ -> (env, None)
            | LoclType lower ->
              (match get_node lower with
              | Tclass ((_, enum_name), _, _)
                when Env.is_enum_class env enum_name ->
                (env, Some enum_name)
              | Tgeneric _ -> compute_enum_name env lower
              | _ -> (env, None))
          )
        | _ ->
          (* Already reported, see Typing_type_wellformedness *)
          (env, None)
      in
      let check_arg env param_kind ((_, pos, arg) as e) opt_param ~is_variadic =
        match opt_param with
        | Some param ->
          (* First check if __ViaLabel is used or if the parameter is
           * a HH\Label.
           *)
          let (env, label_type) =
            let via_label = get_fp_via_label param in
            let ety = param.fp_type.et_type in
            let (env, ety) = Env.expand_type env ety in
            let is_label =
              match get_node ety with
              | Tnewtype (name, _, _) ->
                String.equal SN.Classes.cEnumClassLabel name
              | _ -> false
            in
            match arg with
            | EnumClassLabel (None, label_name) when via_label || is_label ->
              (match get_node ety with
              | Tnewtype (name, [ty_enum; _ty_interface], _)
                when String.equal name SN.Classes.cMemberOf
                     || String.equal name SN.Classes.cEnumClassLabel ->
                let ctor = name in
                (match compute_enum_name env ty_enum with
                | (env, None) -> (env, EnumClassLabelOps.ClassNotFound)
                | (env, Some enum_name) ->
                  EnumClassLabelOps.expand
                    pos
                    env
                    ~full:false
                    ~ctor
                    enum_name
                    label_name)
              | _ ->
                (* Already reported, see Typing_type_wellformedness *)
                (env, EnumClassLabelOps.Invalid))
            | EnumClassLabel (Some _, _) ->
              (* Full info is here, use normal inference *)
              (env, EnumClassLabelOps.Skip)
            | Class_const _ when via_label ->
              Errors.enum_class_label_invalid_argument pos ~is_proj:true;
              (env, EnumClassLabelOps.Invalid)
            | _ when via_label ->
              Errors.enum_class_label_invalid_argument pos ~is_proj:false;
              (env, EnumClassLabelOps.Invalid)
            | _ -> (env, EnumClassLabelOps.Skip)
          in
          let (env, te, ty) =
            match label_type with
            | EnumClassLabelOps.Success (te, ty)
            | EnumClassLabelOps.LabelNotFound (te, ty) ->
              (env, te, ty)
            | _ ->
              let expected =
                ExpectedTy.make_and_allow_coercion_opt
                  env
                  pos
                  Reason.URparam
                  param.fp_type
              in
              expr
                ~accept_using_var:(get_fp_accept_disposable param)
                ?expected
                env
                e
          in
          let (env, err_opt) =
            call_param env param param_kind (e, ty) ~is_variadic
          in
          (env, Some (hole_on_err ~err_opt te, ty))
        | None ->
          let expected =
            ExpectedTy.make pos Reason.URparam (Typing_utils.mk_tany env pos)
          in
          let (env, te, ty) = expr ~expected env e in
          (env, Some (te, ty))
      in
      let set_tyvar_variance_from_lambda_param env opt_param =
        match opt_param with
        | Some param ->
          let rec set_params_variance env ty =
            let (env, ty) = Env.expand_type env ty in
            match get_node ty with
            | Tunion [ty] -> set_params_variance env ty
            | Toption ty -> set_params_variance env ty
            | Tfun { ft_params; ft_ret; _ } ->
              let env =
                List.fold
                  ~init:env
                  ~f:(fun env param ->
                    Env.set_tyvar_variance env param.fp_type.et_type)
                  ft_params
              in
              Env.set_tyvar_variance env ft_ret.et_type ~flip:true
            | _ -> env
          in
          set_params_variance env param.fp_type.et_type
        | None -> env
      in
      (* Given an expected function type ft, check types for the non-unpacked
       * arguments. Don't check lambda expressions if check_lambdas=false *)
      let rec check_args check_lambdas env el paraml =
        match el with
        (* We've got an argument *)
        | ((pk, e), opt_result) :: el ->
          (* Pick up next parameter type info *)
          let (is_variadic, opt_param, paraml) = get_next_param_info paraml in
          let (env, one_result) =
            match (check_lambdas, is_lambda e) with
            | (false, false)
            | (true, true) ->
              check_arg env pk e opt_param ~is_variadic
            | (false, true) ->
              let env = set_tyvar_variance_from_lambda_param env opt_param in
              (env, opt_result)
            | (true, false) -> (env, opt_result)
          in
          let (env, rl, paraml) = check_args check_lambdas env el paraml in
          (env, ((pk, e), one_result) :: rl, paraml)
        | [] -> (env, [], paraml)
      in
      (* Same as above, but checks the types of the implicit arguments, which are
       * read from the context *)
      let check_implicit_args env =
        let capability =
          Typing_coeffects.get_type ft.ft_implicit_params.capability
        in
        if not (TypecheckerOptions.call_coeffects (Env.get_tcopt env)) then
          env
        else
          let env_capability =
            Env.get_local_check_defined env (pos, Typing_coeffects.capability_id)
          in
          Type.sub_type
            pos
            Reason.URnone
            env
            env_capability
            capability
            (fun ?code:_ ?quickfixes:_ _ _ ->
              Errors.call_coeffect_error
                pos
                ~available_incl_unsafe:
                  (Typing_coeffects.pretty env env_capability)
                ~available_pos:(Typing_defs.get_pos env_capability)
                ~required_pos:(Typing_defs.get_pos capability)
                ~required:(Typing_coeffects.pretty env capability))
      in
      let should_forget_fakes =
        (* If the function doesn't have write priveleges to properties, fake
           members cannot be reassigned, so their refinements stand. *)
        let capability =
          Typing_coeffects.get_type ft.ft_implicit_params.capability
        in
        SubType.is_sub_type
          env
          capability
          (MakeType.capability Reason.Rnone SN.Capabilities.writeProperty)
      in

      (* First check the non-lambda arguments. For generic functions, this
       * is likely to resolve type variables to concrete types *)
      let rl = List.map el ~f:(fun e -> (e, None)) in
      let (env, rl, _) = check_args false env rl ft.ft_params in
      (* Now check the lambda arguments, hopefully with type variables resolved *)
      let (env, rl, paraml) = check_args true env rl ft.ft_params in
      (* We expect to see results for all arguments after this second pass *)
      let get_param ((pk, _), opt) =
        match opt with
        | Some (e, ty) -> ((pk, e), ty)
        | None -> failwith "missing parameter in check_args"
      in
      let (tel, _) =
        let l = List.map rl ~f:get_param in
        List.unzip l
      in
      let env = check_implicit_args env in
      let (env, typed_unpack_element, arity, did_unpack) =
        match unpacked_element with
        | None -> (env, None, List.length el, false)
        | Some e ->
          (* Now that we're considering an splat (Some e) we need to construct a type that
           * represents the remainder of the function's parameters. `paraml` represents those
           * remaining parameters, and the variadic parameter is stored in `var_param`. For example, given
           *
           * function f(int $i, string $j, float $k = 3.14, mixed ...$m): void {}
           * function g((string, float, bool) $t): void {
           *   f(3, ...$t);
           * }
           *
           * the constraint type we want is splat([#1], [opt#2], #3).
           *)
          let (consumed, required_params, optional_params) =
            split_remaining_params_required_optional ft paraml
          in
          let (_, p1, _) = e in
          let (env, (d_required, d_optional, d_variadic)) =
            generate_splat_type_vars
              env
              p1
              required_params
              optional_params
              var_param
          in
          let destructure_ty =
            ConstraintType
              (mk_constraint_type
                 ( Reason.Runpack_param (p1, pos_def, consumed),
                   Tdestructure
                     {
                       d_required;
                       d_optional;
                       d_variadic;
                       d_kind = SplatUnpack;
                     } ))
          in
          let (env, te, ty) = expr env e in
          (* Populate the type variables from the expression in the splat *)
          let env_res =
            Type.sub_type_i_res
              p1
              Reason.URparam
              env
              (LoclType ty)
              destructure_ty
              Errors.unify_error
          in
          let (env, te) =
            match env_res with
            | Error env ->
              (* Our type cannot be destructured, add a hole with `nothing`
                 as expected type *)
              let ty_expect =
                MakeType.nothing
                @@ Reason.Rsolve_fail (Pos_or_decl.of_raw_pos pos)
              in
              (env, mk_hole te ~ty_have:ty ~ty_expect)
            | Ok env ->
              (* We have a type that can be destructured so continue and use
                 the type variables for the remaining parameters *)
              let (env, err_opts) =
                List.fold2_exn
                  ~init:(env, [])
                  d_required
                  required_params
                  ~f:(fun (env, errs) elt param ->
                    let (env, err_opt) =
                      call_param
                        env
                        param
                        Ast_defs.Pnormal
                        (e, elt)
                        ~is_variadic:false
                    in
                    (env, err_opt :: errs))
              in
              let (env, err_opts) =
                List.fold2_exn
                  ~init:(env, err_opts)
                  d_optional
                  optional_params
                  ~f:(fun (env, errs) elt param ->
                    let (env, err_opt) =
                      call_param
                        env
                        param
                        Ast_defs.Pnormal
                        (e, elt)
                        ~is_variadic:false
                    in
                    (env, err_opt :: errs))
              in
              let (env, var_err_opt) =
                Option.map2 d_variadic var_param ~f:(fun v vp ->
                    call_param env vp Ast_defs.Pnormal (e, v) ~is_variadic:true)
                |> Option.value ~default:(env, None)
              in
              let subtyping_errs = (List.rev err_opts, var_err_opt) in
              let te =
                match (List.filter_map ~f:Fn.id err_opts, var_err_opt) with
                | ([], None) -> te
                | _ ->
                  let (_, pos, _) = te in
                  hole_on_err
                    te
                    ~err_opt:(Some (ty, pack_errs pos ty subtyping_errs))
              in
              (env, te)
          in

          ( env,
            Some te,
            List.length el + List.length d_required,
            Option.is_some d_variadic )
      in
      (* If we unpacked an array, we don't check arity exactly. Since each
       * unpacked array consumes 1 or many parameters, it is nonsensical to say
       * that not enough args were passed in (so we don't do the min check).
       *)
      let () = check_arity ~did_unpack pos pos_def ft arity in
      (* Variadic params cannot be inout so we can stop early *)
      let env = wfold_left2 inout_write_back env ft.ft_params el in
      (env, (tel, typed_unpack_element, ft.ft_ret.et_type, should_forget_fakes))
    | (r, Tvar _)
      when TypecheckerOptions.method_call_inference (Env.get_tcopt env) ->
      (*
            Typecheck calls with unresolved function type by constructing a
            suitable function type from the arguments and invoking subtyping.
          *)
      let (env, typed_el, type_of_el) =
        argument_list_exprs (expr ~accept_using_var:true) env el
      in
      let (env, typed_unpacked_element, type_of_unpacked_element) =
        match unpacked_element with
        | Some unpacked ->
          let (env, typed_unpacked, type_of_unpacked) =
            expr ~accept_using_var:true env unpacked
          in
          (env, Some typed_unpacked, Some type_of_unpacked)
        | None -> (env, None, None)
      in
      let mk_function_supertype env pos (type_of_el, type_of_unpacked_element) =
        let mk_fun_param ty =
          let flags =
            (* Keep supertype as permissive as possible: *)
            make_fp_flags
              ~mode:FPnormal (* TODO: deal with `inout` parameters *)
              ~accept_disposable:false (* TODO: deal with disposables *)
              ~has_default:false
              ~ifc_external:false
              ~ifc_can_call:false
              ~via_label:false
              ~readonly:false
          in
          {
            fp_pos = Pos_or_decl.of_raw_pos pos;
            fp_name = None;
            fp_type = MakeType.enforced ty;
            fp_flags = flags;
          }
        in
        let ft_arity =
          match type_of_unpacked_element with
          | Some type_of_unpacked ->
            let fun_param = mk_fun_param type_of_unpacked in
            Fvariadic fun_param
          | None -> Fstandard
        in
        (* TODO: ensure `ft_params`/`ft_where_constraints` don't affect subtyping *)
        let ft_tparams = [] in
        let ft_where_constraints = [] in
        let ft_params = List.map ~f:mk_fun_param type_of_el in
        let ft_implicit_params =
          {
            capability =
              CapDefaults (Pos_or_decl.of_raw_pos pos)
              (* TODO(coeffects) should this be a different type? *);
          }
        in
        let (env, return_ty) = Env.fresh_type env pos in
        let return_ty =
          match in_await with
          | None -> return_ty
          | Some r -> MakeType.awaitable r return_ty
        in
        let ft_ret = MakeType.enforced return_ty in
        let ft_flags =
          (* Keep supertype as permissive as possible: *)
          make_ft_flags
            Ast_defs.FSync (* `FSync` fun can still return `Awaitable<_>` *)
            ~return_disposable:false (* TODO: deal with disposable return *)
            ~returns_readonly:false
            ~readonly_this:false
            ~support_dynamic_type:false
        in
        let ft_ifc_decl = Typing_defs_core.default_ifc_fun_decl in
        let fun_locl_type =
          {
            ft_arity;
            ft_tparams;
            ft_where_constraints;
            ft_params;
            ft_implicit_params;
            ft_ret;
            ft_flags;
            ft_ifc_decl;
          }
        in
        let fun_type = mk (r, Tfun fun_locl_type) in
        let env = Env.set_tyvar_variance env fun_type in
        (env, fun_type, return_ty)
      in
      let (env, fun_type, return_ty) =
        mk_function_supertype env pos (type_of_el, type_of_unpacked_element)
      in
      let env =
        Type.sub_type pos Reason.URnone env efty fun_type Errors.unify_error
      in
      let should_forget_fakes = true in
      (env, (typed_el, typed_unpacked_element, return_ty, should_forget_fakes))
    | _ ->
      bad_call env pos efty;
      let env = call_untyped_unpack env (get_pos efty) unpacked_element in
      let should_forget_fakes = true in
      (env, ([], None, err_witness env pos, should_forget_fakes))

and call_untyped_unpack env f_pos unpacked_element =
  match unpacked_element with
  (* In the event that we don't have a known function call type, we can still
   * verify that any unpacked arguments (`...$args`) are something that can
   * be actually unpacked. *)
  | None -> env
  | Some e ->
    let (env, _, ety) = expr env e ~allow_awaitable:(*?*) false in
    let (_, p, _) = e in
    let (env, ty) = Env.fresh_type env p in
    let destructure_ty =
      MakeType.simple_variadic_splat (Reason.Runpack_param (p, f_pos, 0)) ty
    in
    Type.sub_type_i
      p
      Reason.URnone
      env
      (LoclType ety)
      destructure_ty
      Errors.unify_error

(**
 * Build an environment for the true or false branch of
 * conditional statements.
 *)
and condition ?lhs_of_null_coalesce env tparamet ((ty, p, e) as te : Tast.expr)
    =
  let condition = condition ?lhs_of_null_coalesce in
  match e with
  | Aast.Hole (e, _, _, _) -> condition env tparamet e
  | Aast.True when not tparamet ->
    (LEnv.drop_cont env C.Next, Local_id.Set.empty)
  | Aast.False when tparamet -> (LEnv.drop_cont env C.Next, Local_id.Set.empty)
  | Aast.Call ((_, _, Aast.Id (_, func)), _, [(_, te)], None)
    when String.equal SN.StdlibFunctions.is_null func ->
    condition_nullity ~nonnull:(not tparamet) env te
  | Aast.Binop ((Ast_defs.Eqeq | Ast_defs.Eqeqeq), (_, _, Aast.Null), e)
  | Aast.Binop ((Ast_defs.Eqeq | Ast_defs.Eqeqeq), e, (_, _, Aast.Null)) ->
    condition_nullity ~nonnull:(not tparamet) env e
  | Aast.Lvar _
  | Aast.Obj_get _
  | Aast.Class_get _
  | Aast.Binop (Ast_defs.Eq None, _, _) ->
    let (env, ety) = Env.expand_type env ty in
    (match get_node ety with
    | Tprim Tbool -> (env, Local_id.Set.empty)
    | _ -> condition_nullity ~nonnull:tparamet env te)
  | Aast.Binop (((Ast_defs.Diff | Ast_defs.Diff2) as op), e1, e2) ->
    let op =
      if Ast_defs.(equal_bop op Diff) then
        Ast_defs.Eqeq
      else
        Ast_defs.Eqeqeq
    in
    condition env (not tparamet) (ty, p, Aast.Binop (op, e1, e2))
  (* Conjunction of conditions. Matches the two following forms:
      if (cond1 && cond2)
      if (!(cond1 || cond2))
  *)
  | Aast.Binop (((Ast_defs.Ampamp | Ast_defs.Barbar) as bop), e1, e2)
    when Bool.equal tparamet Ast_defs.(equal_bop bop Ampamp) ->
    let (env, lset1) = condition env tparamet e1 in
    (* This is necessary in case there is an assignment in e2
     * We essentially redo what has been undone in the
     * `Binop (Ampamp|Barbar)` case of `expr` *)
    let (env, _, _) =
      expr env (Tast.to_nast_expr e2) ~allow_awaitable:(*?*) false
    in
    let (env, lset2) = condition env tparamet e2 in
    (env, Local_id.Set.union lset1 lset2)
  (* Disjunction of conditions. Matches the two following forms:
      if (cond1 || cond2)
      if (!(cond1 && cond2))
  *)
  | Aast.Binop (((Ast_defs.Ampamp | Ast_defs.Barbar) as bop), e1, e2)
    when Bool.equal tparamet Ast_defs.(equal_bop bop Barbar) ->
    let (env, lset1, lset2) =
      branch
        env
        (fun env ->
          (* Either cond1 is true and we don't know anything about cond2... *)
          condition env tparamet e1)
        (fun env ->
          (* ... Or cond1 is false and therefore cond2 must be true *)
          let (env, _lset) = condition env (not tparamet) e1 in
          (* Similarly to the conjunction case, there might be an assignment in
             cond2 which we must account for. Again we redo what has been undone in
             the `Binop (Ampamp|Barbar)` case of `expr` *)
          let (env, _, _) =
            expr env (Tast.to_nast_expr e2) ~allow_awaitable:(*?*) false
          in
          condition env tparamet e2)
    in
    (env, Local_id.Set.union lset1 lset2)
  | Aast.Call ((_, p, Aast.Id (_, f)), _, [(_, lv)], None)
    when tparamet && String.equal f SN.StdlibFunctions.is_dict_or_darray ->
    safely_refine_is_array env HackDictOrDArray p f lv
  | Aast.Call ((_, p, Aast.Id (_, f)), _, [(_, lv)], None)
    when tparamet && String.equal f SN.StdlibFunctions.is_vec_or_varray ->
    safely_refine_is_array env HackVecOrVArray p f lv
  | Aast.Call ((_, p, Aast.Id (_, f)), _, [(_, lv)], None)
    when tparamet && String.equal f SN.StdlibFunctions.is_any_array ->
    safely_refine_is_array env AnyArray p f lv
  | Aast.Call ((_, p, Aast.Id (_, f)), _, [(_, lv)], None)
    when tparamet && String.equal f SN.StdlibFunctions.is_php_array ->
    safely_refine_is_array env PHPArray p f lv
  | Aast.Call
      ( ( _,
          _,
          Aast.Class_const ((_, _, Aast.CI (_, class_name)), (_, method_name))
        ),
        _,
        [(_, shape); (_, field)],
        None )
    when tparamet
         && String.equal class_name SN.Shapes.cShapes
         && String.equal method_name SN.Shapes.keyExists ->
    key_exists env p shape field
  | Aast.Unop (Ast_defs.Unot, e) -> condition env (not tparamet) e
  | Aast.Is (ivar, h) ->
    let reason = Reason.Ris (fst h) in
    let refine_type env ivar ivar_ty hint_ty =
      let (env, refined_ty) =
        refine_and_simplify_intersection env p reason (fst ivar) ivar_ty hint_ty
      in
      (set_local env ivar refined_ty, Local_id.Set.singleton (snd ivar))
    in
    let (env, lset) =
      match snd h with
      | Aast.Hnonnull -> condition_nullity ~nonnull:tparamet env ivar
      | Aast.Hprim Tnull -> condition_nullity ~nonnull:(not tparamet) env ivar
      | _ -> (env, Local_id.Set.empty)
    in
    let (env, locl) =
      make_a_local_of ~include_this:true env (Tast.to_nast_expr ivar)
    in
    (match locl with
    | Some locl_ivar ->
      let (env, hint_ty) =
        Phase.localize_hint_no_subst env ~ignore_errors:false h
      in
      let (env, hint_ty) =
        if not tparamet then
          Inter.negate_type env reason hint_ty ~approx:TUtils.ApproxUp
        else
          (env, hint_ty)
      in
      refine_type env locl_ivar (fst3 ivar) hint_ty
    | None -> (env, lset))
  | _ -> (env, Local_id.Set.empty)

and string2 env idl =
  let (env, tel) =
    List.fold_left idl ~init:(env, []) ~f:(fun (env, tel) x ->
        let (env, te, ty) = expr env x ~allow_awaitable:(*?*) false in
        let (_, p, _) = x in
        if
          TypecheckerOptions.enable_strict_string_concat_interp
            (Env.get_tcopt env)
        then
          let r = Reason.Rinterp_operand p in
          let (env, formatter_tyvar) = Env.fresh_type_invariant env p in
          let stringlike =
            MakeType.union
              r
              [
                MakeType.arraykey r;
                MakeType.dynamic r;
                MakeType.new_type r SN.Classes.cHHFormatString [formatter_tyvar];
              ]
          in
          let (env, err_opt) =
            Result.fold
              ~ok:(fun env -> (env, None))
              ~error:(fun env -> (env, Some (ty, stringlike)))
            @@ Typing_ops.sub_type_res
                 p
                 Reason.URstr_interp
                 env
                 ty
                 stringlike
                 Errors.strict_str_interp_type_mismatch
          in
          (env, hole_on_err ~err_opt te :: tel)
        else
          let env = Typing_substring.sub_string p env ty in
          (env, te :: tel))
  in
  (env, List.rev tel)

and user_attribute env ua =
  let (env, typed_ua_params) =
    List.map_env env ua.ua_params ~f:(fun env e ->
        let (env, te, _) = expr env e ~allow_awaitable:(*?*) false in
        (env, te))
  in
  (env, { Aast.ua_name = ua.ua_name; Aast.ua_params = typed_ua_params })

and file_attributes env file_attrs =
  (* Disable checking of error positions, as file attributes have spans that
   * aren't subspans of the class or function into which they are copied *)
  Errors.run_with_span Pos.none @@ fun () ->
  List.map_env env file_attrs ~f:(fun env fa ->
      let (env, user_attributes) =
        attributes_check_def env SN.AttributeKinds.file fa.fa_user_attributes
      in
      let env = set_tcopt_unstable_features env fa in
      ( env,
        {
          Aast.fa_user_attributes = user_attributes;
          Aast.fa_namespace = fa.fa_namespace;
        } ))

and type_param env t =
  let (env, user_attributes) =
    attributes_check_def env SN.AttributeKinds.typeparam t.tp_user_attributes
  in
  let (env, tp_parameters) = List.map_env env t.tp_parameters ~f:type_param in
  ( env,
    {
      Aast.tp_variance = t.tp_variance;
      Aast.tp_name = t.tp_name;
      Aast.tp_parameters;
      Aast.tp_constraints = t.tp_constraints;
      Aast.tp_reified = reify_kind t.tp_reified;
      Aast.tp_user_attributes = user_attributes;
    } )

(* Calls the method of a class, but allows the f callback to override the
 * return value type *)
and overload_function
    make_call fpos p env class_id method_id el unpacked_element f =
  let (env, _tal, tcid, ty) = class_expr env [] class_id in
  let (env, _tel, _) =
    argument_list_exprs (expr ~allow_awaitable:false (*?*)) env el
  in
  let (env, (fty, tal)) =
    class_get
      ~is_method:true
      ~is_const:false
      ~coerce_from_ty:None
      env
      ty
      method_id
      class_id
  in
  let (env, (tel, typed_unpack_element, res, should_forget_fakes)) =
    call ~expected:None p env fty el unpacked_element
  in
  let (env, ty) = f env fty res el in
  let (env, fty) = Env.expand_type env fty in
  let fty =
    map_ty fty ~f:(function
        | Tfun ft -> Tfun { ft with ft_ret = MakeType.unenforced ty }
        | ty -> ty)
  in
  let te = Tast.make_typed_expr fpos fty (Aast.Class_const (tcid, method_id)) in
  (make_call env te tal tel typed_unpack_element ty, should_forget_fakes)

and update_array_type ?lhs_of_null_coalesce p env e1 valkind =
  match valkind with
  | `lvalue
  | `lvalue_subexpr ->
    let (env, te1, ty1) =
      raw_expr
        ~valkind:`lvalue_subexpr
        ~check_defined:true
        env
        e1
        ~allow_awaitable:(*?*) false
    in
    begin
      match e1 with
      | (_, _, Lvar (_, x)) ->
        (* type_mapper has updated the type in ty1 typevars, but we
             need to update the local variable type too *)
        let env = set_local env (p, x) ty1 in
        (env, te1, ty1)
      | _ -> (env, te1, ty1)
    end
  | _ -> raw_expr ?lhs_of_null_coalesce env e1 ~allow_awaitable:(*?*) false

(* External API *)
let expr ?expected env e =
  Typing_env.with_origin2 env Decl_counters.Body (fun env ->
      expr ?expected env e ~allow_awaitable:(*?*) false)

let expr_with_pure_coeffects ?expected env e =
  Typing_env.with_origin2 env Decl_counters.Body (fun env ->
      expr_with_pure_coeffects ?expected env e ~allow_awaitable:(*?*) false)

let stmt env st =
  Typing_env.with_origin env Decl_counters.Body (fun env -> stmt env st)
