Skip to content

Commit 281bde1

Browse files
vassilmladenovmeta-codesync[bot]
authored andcommitted
Look through aliases when checking typedefs
Summary: Recursion checks are broken on newtypes because we don't model how the runtime resolves alias definitions, and in localization we conflate interpretation of values typed as aliases with alias resolution for well-formedness checks. As a result, it is trivial to write definitions that pass `hh` but fatal the runtime, and we fail to properly distinguish recursive and non-recursive case types for gating. This change attempts to bring us closer to what happens at runtime when the backing type structure for a typedef is resolved. When checking a typedef value, we fully expand all aliases. If a case type is reached and we started from an alias, we stop and treat it as opaque. If we started from a case type, we expand case types until we reach the root type. For some intuition, consider the type_structure models of the following two types ``` hphpd> case type A = shape('a' => ?B); hphpd> case type B = shape('b' => ?A); hphpd> =type_structure('A') Dict ( [kind] => 31 [union_types] => Vec ( [0] => Dict ( [kind] => 14 [fields] => Dict ( [a] => Dict ( [kind] => 31 [union_types] => Vec ( [0] => Dict ( [kind] => 14 [fields] => Dict ( [b] => Dict ( [kind] => 32 [case_type] => "A" ) ) ) ) [case_type] => "B" [nullable] => true ) ) ) ) [case_type] => "A" ) hphpd> =type_structure('B') Dict ( [kind] => 31 [union_types] => Vec ( [0] => Dict ( [kind] => 14 [fields] => Dict ( [b] => Dict ( [kind] => 31 [union_types] => Vec ( [0] => Dict ( [kind] => 14 [fields] => Dict ( [a] => Dict ( [kind] => 32 [case_type] => "B" ) ) ) ) [case_type] => "A" [nullable] => true ) ) ) ) [case_type] => "B" ) hphpd> ``` Lift out the alias constraint checking so that cycle detection occurs regardless of whether we have resolved to a `Rhs` value. This fixes a case where you can launder well-formedness errors through newtype. Reviewed By: viratyosin Differential Revision: D91863233 fbshipit-source-id: 00ab9e78bc8f80d7a454dc558a421fa349b4aa55
1 parent e34fecd commit 281bde1

33 files changed

Lines changed: 217 additions & 44 deletions

hphp/hack/src/oxidized/copy_types.txt

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -109,7 +109,6 @@ reason_collector::LoggedType
109109
type_counter::Category
110110
type_counter::LoggedType
111111
typing_defs::ClassConstKind
112-
typing_defs::VisibilityBehavior
113112
typing_defs_core::ConsistentKind
114113
typing_defs_core::DestructureKind
115114
typing_defs_core::Enforcement

hphp/hack/src/oxidized/gen/typing_defs.rs

Lines changed: 4 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -3,12 +3,11 @@
33
// This source code is licensed under the MIT license found in the
44
// LICENSE file in the "hack" directory of this source tree.
55
//
6-
// @generated SignedSource<<9b313221c57bf31108da347472a57cab>>
6+
// @generated SignedSource<<e269113b4bb46a8bb1439e75a1b4a68b>>
77
//
88
// To regenerate this file, run:
99
// buck run @fbcode//mode/dev-nosan-lg fbcode//hphp/hack/src:oxidized_regen
1010

11-
use arena_trait::TrivialDrop;
1211
use eq_modulo_pos::EqModuloPos;
1312
use no_pos_hash::NoPosHash;
1413
use ocamlrep::FromOcamlRep;
@@ -602,13 +601,11 @@ pub enum DeserializationError {
602601

603602
#[derive(
604603
Clone,
605-
Copy,
606604
Debug,
607605
Deserialize,
608606
Eq,
609607
EqModuloPos,
610608
FromOcamlRep,
611-
FromOcamlRepIn,
612609
Hash,
613610
NoPosHash,
614611
Ord,
@@ -618,14 +615,14 @@ pub enum DeserializationError {
618615
ToOcamlRep
619616
)]
620617
#[rust_to_ocaml(attr = "deriving show { with_path = false }")]
621-
#[repr(u8)]
618+
#[repr(C, u8)]
622619
pub enum VisibilityBehavior {
623620
#[rust_to_ocaml(name = "Always_expand_newtype")]
624621
AlwaysExpandNewtype,
625622
#[rust_to_ocaml(name = "Expand_visible_newtype_only")]
626623
ExpandVisibleNewtypeOnly,
627624
#[rust_to_ocaml(name = "Never_expand_newtype")]
628625
NeverExpandNewtype,
626+
#[rust_to_ocaml(name = "Resolve_type_structure")]
627+
ResolveTypeStructure(Option<String>),
629628
}
630-
impl TrivialDrop for VisibilityBehavior {}
631-
arena_deserializer::impl_deserialize_in_arena!(VisibilityBehavior);

hphp/hack/src/typing/env/typing_env.ml

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -818,6 +818,8 @@ module M = struct
818818
| CaseType _ ->
819819
(match visibility_behavior with
820820
| Always_expand_newtype -> true
821+
| Resolve_type_structure None -> false
822+
| Resolve_type_structure (Some t) -> String.(t <> name)
821823
| Expand_visible_newtype_only
822824
| Never_expand_newtype ->
823825
false)
@@ -826,7 +828,9 @@ module M = struct
826828
(match td_vis with
827829
| Aast.Opaque ->
828830
(match visibility_behavior with
829-
| Always_expand_newtype -> true
831+
| Resolve_type_structure _
832+
| Always_expand_newtype ->
833+
true
830834
| Never_expand_newtype -> false
831835
| Expand_visible_newtype_only ->
832836
let td_path = Naming_provider.get_typedef_path (get_ctx env) name in
@@ -835,7 +839,9 @@ module M = struct
835839
| None -> (* Not the right place to raise an error *) false))
836840
| Aast.OpaqueModule ->
837841
(match visibility_behavior with
838-
| Always_expand_newtype -> true
842+
| Resolve_type_structure _
843+
| Always_expand_newtype ->
844+
true
839845
| Never_expand_newtype -> false
840846
| Expand_visible_newtype_only ->
841847
Option.equal

hphp/hack/src/typing/typing_defs.ml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -292,6 +292,7 @@ type visibility_behavior =
292292
| Always_expand_newtype
293293
| Expand_visible_newtype_only
294294
| Never_expand_newtype
295+
| Resolve_type_structure of string option
295296
[@@deriving show { with_path = false }]
296297

297298
let is_default_visibility_behaviour = function

hphp/hack/src/typing/typing_defs.mli

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -212,6 +212,11 @@ type visibility_behavior =
212212
| Always_expand_newtype
213213
| Expand_visible_newtype_only
214214
| Never_expand_newtype
215+
(* Approximate HHVM's resolution of type structures, where types and newtypes are
216+
* fully expanded, but recursive references to the current type become opaque leaf
217+
* nodes i.e. TypeStructureKind::T_recursiveUnion. Where this differs is, for regular
218+
* type alias roots, we do not expand case types. *)
219+
| Resolve_type_structure of string option
215220
[@@deriving show]
216221

217222
val is_default_visibility_behaviour : visibility_behavior -> bool

hphp/hack/src/typing/typing_phase.ml

Lines changed: 21 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -831,12 +831,7 @@ and localize_class_instantiation
831831
else
832832
let tparams = Cls.tparams class_info in
833833
let ((env, err, cycles), tyl) =
834-
localize_targs_constrain_wildcards
835-
~ety_env:
836-
{ ety_env with visibility_behavior = default_visibility_behaviour }
837-
env
838-
tyargs
839-
tparams
834+
localize_targs_constrain_wildcards ~ety_env env tyargs tparams
840835
in
841836
(* Hide the class type if its internal and outside of the module *)
842837
if
@@ -1498,6 +1493,26 @@ let localize_hint_no_subst_report_cycles_ env ~ignore_errors ?report_cycle h =
14981493
?report_cycle
14991494
h
15001495

1496+
let localize_typedef_structure env ~ignore_errors ~report_cycle name h =
1497+
let (pos, _) = h in
1498+
let ty = Decl_hint.hint env.decl_env h in
1499+
let ety_env =
1500+
{
1501+
empty_expand_env with
1502+
visibility_behavior = Resolve_type_structure name;
1503+
type_expansions =
1504+
Type_expansions.empty_w_cycle_report ~report_cycle:(Some report_cycle);
1505+
on_error =
1506+
(if ignore_errors then
1507+
None
1508+
else
1509+
Some (Typing_error.Reasons_callback.invalid_type_hint pos));
1510+
wildcard_action = Wildcard_illegal;
1511+
ish_weakening = false;
1512+
}
1513+
in
1514+
localize env ty ~ety_env
1515+
15011516
let localize_hint_no_subst env ~ignore_errors h =
15021517
let ((env, err, _cycles), ty) =
15031518
localize_hint_no_subst_report_cycles_ env ~ignore_errors h

hphp/hack/src/typing/typing_phase.mli

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -110,6 +110,14 @@ val localize_hint_no_subst_report_cycles :
110110
Aast.hint ->
111111
(env * Typing_error.t option * Type_expansions.cycle_reporter list) * locl_ty
112112

113+
val localize_typedef_structure :
114+
env ->
115+
ignore_errors:bool ->
116+
report_cycle:Pos.t * Type_expansions.Expandable.t ->
117+
string option ->
118+
Aast.hint ->
119+
(env * Typing_error.t option * Type_expansions.cycle_reporter list) * locl_ty
120+
113121
val localize_hint_for_refinement :
114122
env -> Aast.hint -> (env * Typing_error.t option) * locl_ty
115123

hphp/hack/src/typing/typing_tdef.ml

Lines changed: 25 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -20,10 +20,11 @@ module MakeType = Typing_make_type
2020

2121
type expansion =
2222
| Rhs of decl_ty
23-
| Opaque of (* constraint *) decl_ty
23+
| Opaque
2424

2525
type expand_result = {
2626
tparams: decl_tparam list;
27+
cstr: decl_ty;
2728
expansion: expansion;
2829
}
2930

@@ -54,6 +55,11 @@ let expand_typedef_decl
5455
force_expand
5556
|| Env.should_expand_type_alias ~visibility_behavior env ~name:x td
5657
in
58+
let cstr =
59+
match td_as_constraint with
60+
| None -> MakeType.mixed (Reason.implicit_upper_bound (td_pos, "?nonnull"))
61+
| Some cstr -> cstr
62+
in
5763
let expansion =
5864
if should_expand then
5965
let td_type =
@@ -65,12 +71,8 @@ let expand_typedef_decl
6571
Rhs td_type
6672
else
6773
Opaque
68-
(match td_as_constraint with
69-
| None ->
70-
MakeType.mixed (Reason.implicit_upper_bound (td_pos, "?nonnull"))
71-
| Some cstr -> cstr)
7274
in
73-
{ tparams = td_tparams; expansion }
75+
{ tparams = td_tparams; cstr; expansion }
7476

7577
(** [expand_typedef_ ~force_expand ety_env env r name ty_args] looks up the type
7678
alias [name] in the decls and returns the value of the type alias as a locl_ty, with
@@ -111,6 +113,7 @@ let expand_typedef_ ~force_expand ety_env env r (x : string) argl :
111113
able to make proper conclusions about the cycle. *)
112114
match ety_env.visibility_behavior with
113115
| Always_expand_newtype -> mk (r, Tnewtype (x, argl, mixed))
116+
| Resolve_type_structure _
114117
| Never_expand_newtype
115118
| Expand_visible_newtype_only ->
116119
mixed
@@ -119,7 +122,7 @@ let expand_typedef_ ~force_expand ety_env env r (x : string) argl :
119122
{ env; ty_err_opt = None; cycles = [cycle]; ty; bound = mixed },
120123
ety_env )
121124
| Ok ety_env ->
122-
let { tparams; expansion } =
125+
let { tparams; cstr; expansion } =
123126
expand_typedef_decl
124127
~force_expand
125128
~visibility_behavior:ety_env.visibility_behavior
@@ -144,22 +147,25 @@ let expand_typedef_ ~force_expand ety_env env r (x : string) argl :
144147
else
145148
let substs = Subst.make_locl tparams argl in
146149
let ety_env = { ety_env with substs } in
150+
let ((env, cstr_err, cstr_cycles), cstr) =
151+
(* Special case for supportdyn<T> defined with "as T" in order to
152+
* avoid supportdynamic.hhi appearing in reason *)
153+
if String.equal x SN.Classes.cSupportDyn then
154+
((env, None, []), List.hd_exn argl)
155+
else
156+
Phase.localize_rec ~ety_env env cstr
157+
in
147158
let ((env, err, cycles), expanded_ty, bound) =
148159
match expansion with
149160
| Rhs ty ->
150-
let ((env, err, cycles), ty) = Phase.localize_rec ~ety_env env ty in
151-
((env, err, cycles), ty, ty)
152-
| Opaque cstr_ty ->
153-
let ((env, err, cycles), cstr_ty) =
154-
(* Special case for supportdyn<T> defined with "as T" in order to
155-
* avoid supportdynamic.hhi appearing in reason *)
156-
if String.equal x SN.Classes.cSupportDyn then
157-
((env, None, []), List.hd_exn argl)
158-
else
159-
Phase.localize_rec ~ety_env env cstr_ty
161+
let ((env, rhs_err, rhs_cycles), ty) =
162+
Phase.localize_rec ~ety_env env ty
160163
in
161-
let ty = mk (r, Tnewtype (x, argl, cstr_ty)) in
162-
((env, err, cycles), ty, cstr_ty)
164+
let err = Option.merge cstr_err rhs_err ~f:Typing_error.both in
165+
((env, err, rhs_cycles @ cstr_cycles), ty, ty)
166+
| Opaque ->
167+
let ty = mk (r, Tnewtype (x, argl, cstr)) in
168+
((env, cstr_err, cstr_cycles), ty, cstr)
163169
in
164170
( { env; ty_err_opt = err; cycles; ty = with_reason expanded_ty r; bound },
165171
ety_env )

hphp/hack/src/typing/typing_typedef.ml

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -208,13 +208,19 @@ let casetype_def env typedef =
208208
env
209209

210210
let check_cycles env (t_pos, t_name) t_assignment hints =
211+
let case_type_root =
212+
match t_assignment with
213+
| SimpleTypeDef _ -> None
214+
| CaseType _ -> Some t_name
215+
in
211216
let (env, cycles) =
212217
List.fold_left hints ~init:(env, []) ~f:(fun (env, cycles) hint ->
213218
let ((env, _ty_err_opt, new_cycles), _ty) =
214-
Phase.localize_hint_no_subst_report_cycles
219+
Phase.localize_typedef_structure
215220
env
216221
~ignore_errors:true
217222
~report_cycle:(t_pos, Type_expansions.Expandable.Type_alias t_name)
223+
case_type_root
218224
hint
219225
in
220226
(env, new_cycles @ cycles))

hphp/hack/test/deps/invoking_funs.php.exp

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,3 +4,4 @@ Type HH\Contexts\Unsafe\globals -> Fun B
44
Type HH\Contexts\Unsafe\read_globals -> Fun A
55
Type HH\Contexts\globals -> Fun B
66
Type HH\Contexts\read_globals -> Fun A, Fun B
7+
Type HH\supportdyn -> Fun B

0 commit comments

Comments
 (0)