2 * Copyright (c) 2018, Facebook, Inc.
5 * This source code is licensed under the MIT license found in the
6 * LICENSE file in the "hack" directory of this source tree.
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
) =
23 | Aast.Happly
((p
, h
), _
) ->
24 begin match Env.get_class env h
with
26 if Cls.kind cls
<> Ast.Cnormal
then
27 Errors.invalid_newable_type_argument tp p
29 (* This case should never happen *)
30 Errors.invalid_newable_type_argument tp p
end
32 if not
@@ Env.get_newable env name
then
33 Errors.invalid_newable_type_argument tp pos
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
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
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"
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
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
95 inherit Tast_visitor.handler_base
97 method! at_expr env x
=
98 (* only considering functions where one or more params are reified *)
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
, _
, _
, _
) ->
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
;
111 match Env.get_class env class_id
with
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
117 | (pos
, _
), New
((_
, CIstatic
), _
, _
, _
, _
) ->
119 let t = Env.get_self_id env
>>=
120 Env.get_class env
>>|
122 tparams_has_reified in
123 Option.iter
t ~f
:(fun has_reified
-> if has_reified
then
124 Errors.new_static_class_reified pos
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
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
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
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
;
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