(* * 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 or Container 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 *) | _ -> None (* Is this type KeyedTraversable * or KeyedContainer * 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 *) let arraykey = MakeType.arraykey (Reason.Rkey_value_collection_key p) in Some (arraykey, ty) | _ -> None (* Is this type varray 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 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 . 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 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 { ... } * class C extends B * and have obj_ty = C and x_ty = B 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 extends B, and we have $x:B for some type t * then $x is C should refine to $x:C. * 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` 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 * and refined_ty is keyset 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(...); 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(...) 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. 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 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(?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 $attributes, * varray $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 ~pos 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) | FVellipsis p -> Errors.ellipsis_strict_mode ~require:`Type_and_param_name pos; (env, Aast.FVellipsis p) | 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 ~pos 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) | FVellipsis _ -> false | _ -> 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 [] | (FVellipsis pos, _) -> (env, Aast.FVellipsis pos) | (_, _) -> (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 -> (*iter2_shortest Unify.unify_param_modes ft.ft_params supplied_params; *) env | Some x -> let var_param = match f.f_variadic with | FVellipsis pos -> let param = TUtils.default_fun_param ~pos:(Pos_or_decl.of_raw_pos pos) (mk (Reason.Rvar_param pos, Typing_defs.make_tany ())) in Some param | _ -> None in let rec iter l1 l2 = match (l1, l2, var_param) with | (_, [], _) -> () | ([], _, None) -> () | ([], (pkx_2, x2) :: rl2, Some def1) -> param_modes ~is_variadic:true def1 x2 pkx_2; iter [] rl2 | (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(...) 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: classname); 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, 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($x)` means we get cascading errors if we have e.g. a return type of keyset. 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: - `` <: `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) 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, 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) * CI id literal class name * 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)