Clean up logic that governs valid reified arguments
[hiphop-php.git] / hphp / hack / src / typing / tast_check / reified_check.ml
blobf2bee86eb83d48eeba5d74b8d0be65950836d53f
1 (**
2 * Copyright (c) 2018, Facebook, Inc.
3 * All rights reserved.
5 * This source code is licensed under the MIT license found in the
6 * LICENSE file in the "hack" directory of this source tree.
8 *)
10 open Core_kernel
11 open Tast
12 open Typing_defs
14 module Env = Tast_env
15 module UA = Naming_special_names.UserAttributes
16 module Cls = Decl_provider.Class
18 let tparams_has_reified tparams =
19 List.exists tparams ~f:(fun tparam -> tparam.tp_reified <> Nast.Erased)
21 let valid_newable_hint env tp (pos, hint) =
22 match hint with
23 | Aast.Happly ((p, h), _) ->
24 begin match Env.get_class env h with
25 | Some cls ->
26 if Cls.kind cls <> Ast.Cnormal then
27 Errors.invalid_newable_type_argument tp p
28 | None ->
29 (* This case should never happen *)
30 Errors.invalid_newable_type_argument tp p end
31 | Aast.Habstr name ->
32 if not @@ Env.get_newable env name then
33 Errors.invalid_newable_type_argument tp pos
34 | _ ->
35 Errors.invalid_newable_type_argument tp pos
37 let verify_has_consistent_bound env (tparam: Tast.tparam) =
38 let upper_bounds = Typing_set.elements (Env.get_upper_bounds env (snd tparam.tp_name)) in
39 let bound_classes = List.filter_map upper_bounds ~f:(function
40 | _, Tclass ((_, class_id), _, _) ->
41 Env.get_class env class_id
42 | _ -> None) in
43 let valid_classes = List.filter bound_classes ~f:Tast_utils.valid_newable_class in
44 if List.length valid_classes <> 1 then
45 let cbs = List.map ~f:(Cls.name) valid_classes in
46 Errors.invalid_newable_type_param_constraints tparam.tp_name cbs
49 (* When passing targs to a reified position, they must either be concrete types
50 * or reified type parameters. This prevents the case of
52 * class C<reify Tc> {}
53 * function f<Tf>(): C<Tf> {}
55 * where Tf does not exist at runtime.
57 let verify_targ_valid env tparam targ =
58 (* There is some subtlety here. If a type *parameter* is declared reified,
59 * even if it is soft, we require that the argument be concrete or reified, not soft
60 * reified or erased *)
61 begin match tparam.tp_reified with
62 | Nast.Reified
63 | Nast.SoftReified ->
64 begin match Env.hint_to_ty env targ with
65 | _, Tapply ((p, h), []) when h = Naming_special_names.Typehints.wildcard ->
66 if not @@ Env.get_allow_wildcards env then
67 Errors.invalid_reified_argument tparam.tp_name (p, h) "a wildcard"
68 | _, Tgeneric t ->
69 let p = fst targ in
70 begin match (Env.get_reified env t) with
71 | Nast.Erased -> Errors.invalid_reified_argument tparam.tp_name (p, t) "not reified"
72 | Nast.SoftReified -> Errors.invalid_reified_argument tparam.tp_name (p, t) "soft reified"
73 | Nast.Reified -> () end
74 | _ -> () end;
75 | Nast.Erased -> () end;
77 begin if Attributes.mem UA.uaEnforceable tparam.tp_user_attributes then
78 Type_test_hint_check.validate_hint env targ
79 (Errors.invalid_enforceable_type "parameter" tparam.tp_name) end;
81 begin if Attributes.mem UA.uaNewable tparam.tp_user_attributes then
82 valid_newable_hint env tparam.tp_name targ end
85 let verify_call_targs env expr_pos decl_pos tparams targs =
86 if tparams_has_reified tparams &&
87 List.is_empty targs then
88 Errors.require_args_reify decl_pos expr_pos;
89 (* Unequal_lengths case handled elsewhere *)
90 List.iter2 tparams targs ~f:begin fun tparam targ ->
91 verify_targ_valid env tparam targ
92 end |> ignore
94 let handler = object
95 inherit Tast_visitor.handler_base
97 method! at_expr env x =
98 (* only considering functions where one or more params are reified *)
99 match x with
100 | (pos, _), Call (_, ((_, (_, Tfun { ft_pos; ft_tparams; _ })), _), targs, _, _) ->
101 let tparams = fst ft_tparams in
102 verify_call_targs env pos ft_pos tparams targs
103 | (pos, _), New (((_, ty), CI (_, class_id)), targs, _, _, _) ->
104 begin match ty with
105 | (_, Tabstract (AKgeneric ci, None)) when ci = class_id ->
106 if not (Env.get_newable env ci) then
107 Errors.new_without_newable pos ci;
108 if not (List.is_empty targs) then
109 Errors.tparam_with_tparam pos ci;
110 | _ ->
111 match Env.get_class env class_id with
112 | Some cls ->
113 let tparams = Cls.tparams cls in
114 let class_pos = Cls.pos cls in
115 verify_call_targs env pos class_pos tparams targs
116 | None -> () end
117 | (pos, _), New ((_, CIstatic), _, _, _, _) ->
118 let open Option in
119 let t = Env.get_self_id env >>=
120 Env.get_class env >>|
121 Cls.tparams >>|
122 tparams_has_reified in
123 Option.iter t ~f:(fun has_reified -> if has_reified then
124 Errors.new_static_class_reified pos
126 | _ -> ()
128 method! at_hint env = function
129 | pos, Aast.Happly ((_, class_id), targs) ->
130 let tc = Env.get_class env class_id in
131 Option.iter tc ~f:(fun tc ->
132 let tparams = Cls.tparams tc in
133 ignore (List.iter2 tparams targs ~f:(verify_targ_valid env));
135 (* TODO: This check could be unified with the existence check above,
136 * but would require some consolidation T38941033. List.iter2 gives
137 * a nice Or_unequal_lengths.t result that replaces this if statement *)
138 let tparams_length = List.length tparams in
139 let targs_length = List.length targs in
140 if tparams_length <> targs_length then
141 if targs_length <> 0
142 then Errors.type_arity pos class_id (string_of_int (tparams_length))
143 else if tparams_has_reified tparams then
144 Errors.require_args_reify (Cls.pos tc) pos
146 | _ ->
149 method! at_tparam env tparam =
150 (* Can't use Attributes.mem here because of a conflict between Nast.user_attributes and Tast.user_attributes *)
151 if List.exists tparam.tp_user_attributes (fun { ua_name; _ } -> UA.uaNewable = snd ua_name) then
152 verify_has_consistent_bound env tparam
154 method! at_class_ env { c_name = (pos, name); _ } =
155 match Env.get_class env name with
156 | Some cls ->
157 begin match Cls.construct cls with
158 | _, Typing_defs.ConsistentConstruct ->
159 if List.exists ~f:(fun t -> t.tp_reified <> Nast.Erased) (Cls.tparams cls) then
160 Errors.consistent_construct_reified pos;
161 | _ -> () end
162 | None -> ()
164 method! at_fun_ _ { f_name = (pos, _); f_tparams; f_variadic; _ } =
165 if List.exists f_tparams ~f:(fun tparam -> tparam.tp_reified <> Erased) &&
166 f_variadic <> FVnonVariadic
167 then Errors.reified_tparam_variadic pos
169 method! at_method_ _ { m_name = (pos, _); m_tparams; m_variadic; _ } =
170 if List.exists m_tparams ~f:(fun tparam -> tparam.tp_reified <> Erased) &&
171 m_variadic <> FVnonVariadic
172 then Errors.reified_tparam_variadic pos