Skip to content

Commit 9b14e5d

Browse files
vassilmladenovmeta-codesync[bot]
authored andcommitted
Properly type HH\type_structure_classname
Summary: The `type_stucture_classname` helper has existed forever as a compilation target, just withheld from typing support. This factors out the type constant resolution logic from the `type_structure` function and applies it to the helper. Reviewed By: mheiber Differential Revision: D95327521 fbshipit-source-id: 4ac84f646960d42c10ff6985f6e72bbac8413c06
1 parent f6f9a7b commit 9b14e5d

16 files changed

Lines changed: 178 additions & 34 deletions

hphp/hack/hhi/typestructure.hhi

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -124,6 +124,15 @@ namespace HH {
124124
// type_structure(C::class or new C, 'type_const_name')
125125
// : TypeStructure
126126

127+
/*
128+
* Replacement for type_structure(C::class, 'T')['classname']
129+
* for type constants that point to classes.
130+
*/
131+
function type_structure_classname(
132+
mixed $cls_or_obj,
133+
string $cns_name,
134+
)[]: \HH\FIXME\MISSING_RETURN_TYPE;
135+
127136
/*
128137
* Retrieves the TypeStructure for a type alias.
129138
*/

hphp/hack/src/naming/naming_special_names.ml

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1095,6 +1095,8 @@ module StdlibFunctions = struct
10951095

10961096
let type_structure = "\\HH\\type_structure"
10971097

1098+
let type_structure_classname = "\\HH\\type_structure_classname"
1099+
10981100
let array_mark_legacy = "\\HH\\array_mark_legacy"
10991101

11001102
let array_unmark_legacy = "\\HH\\array_unmark_legacy"
@@ -1112,6 +1114,7 @@ module StdlibFunctions = struct
11121114
PseudoFunctions.isset;
11131115
PseudoFunctions.unset;
11141116
type_structure;
1117+
type_structure_classname;
11151118
PseudoFunctions.unsafe_cast;
11161119
PseudoFunctions.unsafe_nonnull_cast;
11171120
]

hphp/hack/src/typing/typing.ml

Lines changed: 50 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -5916,8 +5916,7 @@ end = struct
59165916
let result = make_call env te tal tel typed_unpack_element ty in
59175917
(result, should_forget_fakes)
59185918
in
5919-
let type_structure_impl pos e1 e2 =
5920-
let should_forget_fakes = false in
5919+
let type_structure_impl ~fn pos e1 e2 =
59215920
match Aast_utils.arg_to_expr e2 with
59225921
| (_, p, String cst) ->
59235922
(* find the class constant implicitly defined by the typeconst *)
@@ -5952,19 +5951,18 @@ end = struct
59525951
Typing_error.(
59535952
primary
59545953
@@ Primary.Illegal_type_structure
5955-
{ pos; msg = "Could not resolve the type constant" })
5954+
{ pos; msg = "Could not resolve the type constant"; fn })
59565955
| _ -> ()
59575956
in
5958-
(result, should_forget_fakes)
5957+
result
59595958
| _ ->
59605959
Typing_error_utils.add_typing_error
59615960
~env
59625961
Typing_error.(
59635962
primary
59645963
@@ Primary.Illegal_type_structure
5965-
{ pos; msg = "Second argument is not a string" });
5966-
let result = expr_error env pos e in
5967-
(result, should_forget_fakes)
5964+
{ pos; msg = "Second argument is not a string"; fn });
5965+
expr_error env pos e
59685966
in
59695967
match fun_expr with
59705968
(* Special top-level function *)
@@ -6129,8 +6127,52 @@ end = struct
61296127
when String.equal type_structure SN.StdlibFunctions.type_structure
61306128
&& Int.equal (List.length el) 2
61316129
&& Option.is_none unpacked_element ->
6130+
let should_forget_fakes = false in
6131+
(match el with
6132+
| [e1; e2] ->
6133+
let result = type_structure_impl ~fn:type_structure pos e1 e2 in
6134+
(result, should_forget_fakes)
6135+
| _ -> assert false)
6136+
| type_structure_classname
6137+
when String.equal
6138+
type_structure_classname
6139+
SN.StdlibFunctions.type_structure_classname
6140+
&& Int.equal (List.length el) 2
6141+
&& Option.is_none unpacked_element ->
6142+
let should_forget_fakes = false in
61326143
(match el with
6133-
| [e1; e2] -> type_structure_impl pos e1 e2
6144+
| [e1; e2] ->
6145+
let (env, te, const_ty) =
6146+
type_structure_impl ~fn:type_structure_classname pos e1 e2
6147+
in
6148+
let (env, const_ty) = Env.expand_type env const_ty in
6149+
let (env, const_ty) =
6150+
Typing_dynamic_utils.strip_dynamic env const_ty
6151+
in
6152+
let result =
6153+
match get_node const_ty with
6154+
| Tnewtype (name, [ty_arg], _)
6155+
when String.equal name SN.FB.cTypeStructure ->
6156+
if Typing_structure.is_enum_or_classish env ty_arg then
6157+
let ty = MakeType.classname (get_reason const_ty) [ty_arg] in
6158+
(env, te, ty)
6159+
else begin
6160+
Typing_error_utils.add_typing_error
6161+
~env
6162+
Typing_error.(
6163+
primary
6164+
@@ Primary.Illegal_type_structure
6165+
{
6166+
pos;
6167+
msg =
6168+
"The type constant does not resolve to a classish type";
6169+
fn = type_structure_classname;
6170+
});
6171+
expr_error env pos e
6172+
end
6173+
| _ -> expr_error env pos e
6174+
in
6175+
(result, should_forget_fakes)
61346176
| _ -> assert false)
61356177
| _ -> dispatch_id env id
61366178
end

hphp/hack/src/typing/typing_error.ml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -857,6 +857,7 @@ module Primary = struct
857857
| Illegal_type_structure of {
858858
pos: Pos.t;
859859
msg: string;
860+
fn: string;
860861
}
861862
| Illegal_typeconst_direct_access of Pos.t
862863
| Wrong_expression_kind_attribute of {

hphp/hack/src/typing/typing_error.mli

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -821,6 +821,7 @@ module Primary : sig
821821
| Illegal_type_structure of {
822822
pos: Pos.t;
823823
msg: string;
824+
fn: string;
824825
}
825826
| Illegal_typeconst_direct_access of Pos.t
826827
| Wrong_expression_kind_attribute of {

hphp/hack/src/typing/typing_error_utils.ml

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -2949,11 +2949,13 @@ end = struct
29492949
let claim = lazy (p, "Not a valid class name") in
29502950
create ~code:Error_code.InvalidClassname ~claim ()
29512951

2952-
let illegal_type_structure pos msg =
2952+
let illegal_type_structure pos msg fn =
29532953
let claim =
29542954
lazy
29552955
(let msg =
2956-
"The two arguments to `type_structure()` must be:"
2956+
"The two arguments to `"
2957+
^ Utils.strip_ns fn
2958+
^ "()` must be:"
29572959
^ "\n - first: `ValidClassname::class` or an object of that class"
29582960
^ "\n - second: a single-quoted string literal containing the name"
29592961
^ " of a type constant of that class\n"
@@ -4989,7 +4991,8 @@ end = struct
49894991
| Cannot_declare_constant { pos; class_pos; class_name } ->
49904992
cannot_declare_constant pos (class_pos, class_name)
49914993
| Invalid_classname pos -> invalid_classname pos
4992-
| Illegal_type_structure { pos; msg } -> illegal_type_structure pos msg
4994+
| Illegal_type_structure { pos; msg; fn } ->
4995+
illegal_type_structure pos msg fn
49934996
| Illegal_typeconst_direct_access pos -> illegal_typeconst_direct_access pos
49944997
| Wrong_expression_kind_attribute
49954998
{

hphp/hack/src/typing/typing_structure.ml

Lines changed: 26 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -55,6 +55,31 @@ let make_ts : Typing_env_types.env -> locl_ty -> Typing_env_types.env * locl_ty
5555
(* Should not hit this because TypeStructure should always be defined *)
5656
(env, MakeType.dynamic r)
5757

58+
(* Does this type contain only enum or object instances?
59+
* These are the types that can be represented using classname
60+
*)
61+
let rec is_enum_or_classish env ty =
62+
match get_node ty with
63+
| Tnewtype (cid, _, _) when Env.is_enum env cid -> true
64+
| Tintersection tys -> List.exists tys ~f:(is_enum_or_classish env)
65+
| Tunion tys -> List.for_all tys ~f:(is_enum_or_classish env)
66+
| Tclass ((_, x), _, _)
67+
when not
68+
(String.equal x SN.Collections.cVec
69+
|| String.equal x SN.Collections.cDict
70+
|| String.equal x SN.Collections.cKeyset) ->
71+
true
72+
| Tgeneric _ ->
73+
let (_env, tyl) =
74+
TUtils.get_concrete_supertypes
75+
~expand_supportdyn:true
76+
~abstract_enum:false
77+
env
78+
ty
79+
in
80+
List.exists tyl ~f:(is_enum_or_classish env)
81+
| _ -> false
82+
5883
let rec transform_shapemap ?(nullable = false) env pos ty shape =
5984
let ((env, ty_err_opt), ty) =
6085
Typing_solver.expand_type_and_solve
@@ -96,28 +121,7 @@ let rec transform_shapemap ?(nullable = false) env pos ty shape =
96121
(* Does this type contain only enum or object instances?
97122
* These are the types that can be represented using classname
98123
*)
99-
let rec is_enum_or_classish ty =
100-
match get_node ty with
101-
| Tnewtype (cid, _, _) when Env.is_enum env cid -> true
102-
| Tintersection tys -> List.exists tys ~f:is_enum_or_classish
103-
| Tunion tys -> List.for_all tys ~f:is_enum_or_classish
104-
| Tclass ((_, x), _, _)
105-
when not
106-
(String.equal x SN.Collections.cVec
107-
|| String.equal x SN.Collections.cDict
108-
|| String.equal x SN.Collections.cKeyset) ->
109-
true
110-
| Tgeneric _ ->
111-
let (_env, tyl) =
112-
TUtils.get_concrete_supertypes
113-
~expand_supportdyn:true
114-
~abstract_enum:false
115-
env
116-
ty
117-
in
118-
List.exists tyl ~f:is_enum_or_classish
119-
| _ -> false
120-
in
124+
let is_enum_or_classish ty = is_enum_or_classish env ty in
121125
let supportdyn = supportdyn || supportdyn_bound in
122126
let transform_shape_field field { sft_ty; sft_optional } (env, shape) =
123127
(* Accumulates the provided type for this iteration of the fold, adding

hphp/hack/src/typing/typing_structure.mli

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,8 @@
66
*
77
*)
88

9+
val is_enum_or_classish : Typing_env_types.env -> Typing_defs.locl_ty -> bool
10+
911
val transform_shapemap :
1012
?nullable:bool ->
1113
Typing_env_types.env ->
Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
<?hh
2+
3+
abstract class A {
4+
abstract const type T;
5+
abstract const type T2 as A;
6+
}
7+
8+
function expect_string(string $s): void {}
9+
class Test {
10+
private static function foo(
11+
classname<A> $kls,
12+
dynamic $d,
13+
): void {
14+
expect_string(type_structure(HH\classname_to_class($kls), 'T')['classname']);
15+
expect_string(HH\type_structure_classname(HH\classname_to_class($kls), 'T'));
16+
17+
expect_string(type_structure(HH\classname_to_class($kls), 'T2')['classname']);
18+
expect_string(HH\type_structure_classname(HH\classname_to_class($kls), 'T2'));
19+
20+
expect_string(type_structure(HH\classname_to_class($d), 'T')['classname']);
21+
expect_string(HH\type_structure_classname(HH\classname_to_class($d), 'T'));
22+
}
23+
}
Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
ERROR: File "classname_tyvar.php", line 14, characters 68-78:
2+
This shape doesn't have a field `classname` (Typing[4108])
3+
File "typestructure.hhi", line 59, characters 31-699:
4+
The shape is defined here
5+
ERROR: File "classname_tyvar.php", line 15, characters 19-45:
6+
The two arguments to `HH\type_structure_classname()` must be:
7+
- first: `ValidClassname::class` or an object of that class
8+
- second: a single-quoted string literal containing the name of a type constant of that class
9+
The type constant does not resolve to a classish type (Typing[4150])
10+
ERROR: File "classname_tyvar.php", line 20, characters 56-57:
11+
Invalid argument (Typing[4110])
12+
File "classname.hhi", line 51, characters 43-63:
13+
Expected `classname<_>`
14+
File "classname_tyvar.php", line 12, characters 5-11:
15+
But got `dynamic`
16+
ERROR: File "classname_tyvar.php", line 21, characters 69-70:
17+
Invalid argument (Typing[4110])
18+
File "classname.hhi", line 51, characters 43-63:
19+
Expected `classname<_>`
20+
File "classname_tyvar.php", line 12, characters 5-11:
21+
But got `dynamic`

0 commit comments

Comments
 (0)