Skip to content

Commit bd41e37

Browse files
Michael Thomasfacebook-github-bot
authored andcommitted
Handle tyvar rank
Summary: We allow monomorphic function types to be subtypes of polymorphic function types e.g. ``` function(mixed): void <: function<T>(T): void ``` However, since we do not disallow type variables in monomorphic function types this rule allows higher-rank type parameters to be present in the bounds of type variables in an outer scope. This diff introduces ranks for type parameters and type variables to avoid this escape. When we reach a subtype proposition involving a higher-rank type (currently this is only polymorphic function types) we increment the rank. This means that when we introduce fresh type parameters (for supertypes) or fresh type variables (for subtypes) and substitute them into a polymorphic function type, those type parameters & type variables will have higher rank than those occuring in the outer scope. When we reach a subtype proposition involving a type variable (i.e. C-Var-L `facebook#1 <: T` or C-Var-R `T <: facebook#1`) we check to ensure that there is no type parameter contained in `T` with a greater rank than the rank of `facebook#1`. This check involves traversing a type, potentially deeply, so we would like to avoid it whenever possible. To do so we add two optimizations: 1) We record when we are in a subtype proposition involving higher rank generics; if we are not then a type _cannot_ contain a higher rank generic and we can avoid the check 2) If we have to perform the check, we avoid fully traversing the type whenever possible by finding the first type parameter with a higher rank. The downside of this will be that when there are multiple higher-rank generics, on the first will be reported and the developer will only see the subsequent errors after the first is fixed. Reviewed By: andrewjkennedy Differential Revision: D72312308 fbshipit-source-id: 02ffef9eeb3c1c176a3ce9b8d47ab74da3abafc7
1 parent b1a0cc0 commit bd41e37

40 files changed

+637
-201
lines changed

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

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@
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<<42c0d811e27b0e64fa2c78c8d3ebe66a>>
6+
// @generated SignedSource<<c1f6f3358eeb2c5c6299c8a5e28c5e7f>>
77
//
88
// To regenerate this file, run:
99
// hphp/hack/src/oxidized_regen.sh
@@ -96,6 +96,7 @@ pub struct TyvarInfo {
9696
pub eager_solve_failed: bool,
9797
pub solving_info: SolvingInfo,
9898
pub is_error: bool,
99+
pub rank: isize,
99100
}
100101

101102
pub type Tvenv = tvid::map::Map<TyvarInfo>;

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

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@
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<<6d74a42dbae601505afb7771e7ba9d09>>
6+
// @generated SignedSource<<8be733e9b0946dd30f0ee749ba761a48>>
77
//
88
// To regenerate this file, run:
99
// hphp/hack/src/oxidized_regen.sh
@@ -45,6 +45,7 @@ pub struct Kind {
4545
pub newable: bool,
4646
pub require_dynamic: bool,
4747
pub parameters: Vec<NamedKind>,
48+
pub rank: isize,
4849
}
4950

5051
#[derive(

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

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@
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<<fd303fd24eb18fc63536e0b7e2ce03db>>
6+
// @generated SignedSource<<84d5e5620eb9e3567cf1f5a85b9433df>>
77
//
88
// To regenerate this file, run:
99
// hphp/hack/src/oxidized_regen.sh
@@ -355,6 +355,8 @@ pub enum PrjAsymm {
355355
PrjAsymmArraykey,
356356
#[rust_to_ocaml(name = "Prj_asymm_num")]
357357
PrjAsymmNum,
358+
#[rust_to_ocaml(name = "Prj_asymm_contains")]
359+
PrjAsymmContains,
358360
}
359361
impl TrivialDrop for PrjAsymm {}
360362
arena_deserializer::impl_deserialize_in_arena!(PrjAsymm);
@@ -595,7 +597,7 @@ pub enum WitnessDecl {
595597
#[rust_to_ocaml(name = "Support_dynamic_type_assume")]
596598
SupportDynamicTypeAssume(pos_or_decl::PosOrDecl),
597599
#[rust_to_ocaml(name = "Polymorphic_type_param")]
598-
PolymorphicTypeParam(pos_or_decl::PosOrDecl, String),
600+
PolymorphicTypeParam(pos_or_decl::PosOrDecl, String, String, isize),
599601
}
600602

601603
/// Axioms are information about types provided by the user in class or type

hphp/hack/src/oxidized/manual/typing_reason_impl.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -103,7 +103,7 @@ impl WitnessDecl {
103103
| Self::IllegalRecursiveType(pos_or_decl, _)
104104
| Self::SupportDynamicTypeAssume(pos_or_decl)
105105
| Self::TupleFromSplat(pos_or_decl)
106-
| Self::PolymorphicTypeParam(pos_or_decl, _) => pos_or_decl,
106+
| Self::PolymorphicTypeParam(pos_or_decl, _, _, _) => pos_or_decl,
107107
}
108108
}
109109
}

hphp/hack/src/oxidized_by_ref/decl_visitor/node_impl_gen.rs

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@
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<<d94a313f4caef34268f65daaae9dd4db>>
6+
// @generated SignedSource<<59328d6da7e3aef2ffffc051b04f3155>>
77
//
88
// To regenerate this file, run:
99
// hphp/hack/src/oxidized_regen.sh
@@ -1672,6 +1672,7 @@ impl<'a> Node<'a> for PrjAsymm {
16721672
PrjAsymm::PrjAsymmNullable => {}
16731673
PrjAsymm::PrjAsymmArraykey => {}
16741674
PrjAsymm::PrjAsymmNum => {}
1675+
PrjAsymm::PrjAsymmContains => {}
16751676
}
16761677
}
16771678
}

hphp/hack/src/oxidized_by_ref/gen/typing_reason.rs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@
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<<7ba37c48d05da313aa88c07f68aa8101>>
6+
// @generated SignedSource<<a10e56f0cfaa28819bd70d9cfa97e5d5>>
77
//
88
// To regenerate this file, run:
99
// hphp/hack/src/oxidized_regen.sh
@@ -548,7 +548,7 @@ pub enum WitnessDecl<'a> {
548548
#[serde(deserialize_with = "arena_deserializer::arena", borrow)]
549549
#[rust_to_ocaml(name = "Polymorphic_type_param")]
550550
#[rust_to_ocaml(inline_tuple)]
551-
PolymorphicTypeParam(&'a (&'a pos_or_decl::PosOrDecl<'a>, &'a str)),
551+
PolymorphicTypeParam(&'a (&'a pos_or_decl::PosOrDecl<'a>, &'a str, &'a str, isize)),
552552
}
553553
impl<'a> TrivialDrop for WitnessDecl<'a> {}
554554
arena_deserializer::impl_deserialize_in_arena!(WitnessDecl<'arena>);

hphp/hack/src/oxidized_by_ref/manual/typing_reason_impl.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -105,7 +105,7 @@ impl<'a> WitnessDecl<'a> {
105105
| WitnessDecl::IllegalRecursiveType((p, _))
106106
| WitnessDecl::SupportDynamicTypeAssume(p)
107107
| WitnessDecl::PessimisedThis(p)
108-
| WitnessDecl::PolymorphicTypeParam((p, _)) => p,
108+
| WitnessDecl::PolymorphicTypeParam((p, _, _, _)) => p,
109109
}
110110
}
111111
}

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

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -93,6 +93,8 @@ let expand_var env r v =
9393
let (inference_env, ty_solution) = Inf.expand_var env.inference_env r v in
9494
({ env with inference_env }, ty_solution)
9595

96+
let rank_of_tvar { inference_env; _ } tvid = Inf.get_rank inference_env tvid
97+
9698
let fresh_type_reason ?variance env p r =
9799
log_env_change_ "fresh_type_reason" env
98100
@@
@@ -109,6 +111,18 @@ let fresh_type env p =
109111
in
110112
({ env with inference_env }, res)
111113

114+
let fresh_type_invariant_with_rank env rank p =
115+
log_env_change_ "fresh_type_invariant_with_rank" env
116+
@@
117+
let (inference_env, res) =
118+
Inf.fresh_type_invariant_with_rank
119+
env.inference_env
120+
env.tvar_id_provider
121+
rank
122+
p
123+
in
124+
({ env with inference_env }, res)
125+
112126
let fresh_type_invariant env p =
113127
log_env_change_ "fresh_type_invariant" env
114128
@@
@@ -500,6 +514,7 @@ let add_fresh_generic_parameter env pos prefix ~reified ~enforceable ~newable =
500514
newable;
501515
require_dynamic = true;
502516
parameters = [];
517+
rank = 0;
503518
}
504519
in
505520
add_fresh_generic_parameter_by_kind env pos prefix kind
@@ -525,6 +540,7 @@ let get_tpenv_tparams env =
525540
require_dynamic = _;
526541
(* FIXME what to do here? it seems dangerous to just traverse *)
527542
parameters = _;
543+
rank = _;
528544
}
529545
acc ->
530546
let folder ty acc =
@@ -2110,3 +2126,5 @@ let with_outside_expr_tree env ~macro_variables f =
21102126
(env, r1, r2, new_macro_type_mapping)
21112127

21122128
let is_in_expr_tree env = Option.is_some env.in_expr_tree
2129+
2130+
let rank_of_tparam env tparam = Type_parameter_env.get_rank env.tpenv tparam

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

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -74,6 +74,10 @@ val fresh_type_reason :
7474
it won't be solved automatically at the end of the scope *)
7575
val fresh_type_invariant : env -> Pos.t -> env * locl_ty
7676

77+
(** Generate a fresh type variable type with a specified ranks for use
78+
in subtyping of rank-n polymorphic types *)
79+
val fresh_type_invariant_with_rank : env -> int -> Pos.t -> env * locl_ty
80+
7781
(** Generate a fresh type variable to stand for an unknown type in the
7882
case of type errors. *)
7983
val fresh_type_error : env -> Pos.t -> env * locl_ty
@@ -642,3 +646,7 @@ val update_ity_reason :
642646
internal_type ->
643647
f:(locl_phase Typing_reason.t_ -> locl_phase Typing_reason.t_) ->
644648
internal_type
649+
650+
val rank_of_tvar : env -> Tvid.t -> int
651+
652+
val rank_of_tparam : env -> string -> int

hphp/hack/src/typing/type_parameter_env.ml

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,9 @@ type t = {
4141
}
4242
[@@deriving hash, show { with_path = false }]
4343

44+
let bindings { tparams; _ } =
45+
List.map ~f:(fun (k, (_, v)) -> (k, v)) (SMap.bindings tparams)
46+
4447
let empty = { tparams = SMap.empty; consistent = true }
4548

4649
let mem name tpenv = SMap.mem name tpenv.tparams
@@ -136,6 +139,11 @@ let get_pos tpenv name =
136139
| None -> Pos_or_decl.none
137140
| Some (pos, _) -> pos
138141

142+
let get_rank tpenv name =
143+
match get name tpenv with
144+
| None -> 0
145+
| Some { rank; _ } -> rank
146+
139147
let get_tparam_names tpenv = SMap.keys tpenv.tparams
140148

141149
let is_consistent tpenv = tpenv.consistent
@@ -167,6 +175,7 @@ let add_upper_bound_ tpenv name ty =
167175
newable = false;
168176
require_dynamic = false;
169177
parameters = [];
178+
rank = 0;
170179
} )
171180
| Some (pos, tp) ->
172181
(pos, { tp with upper_bounds = TySet.add ty tp.upper_bounds })
@@ -191,6 +200,7 @@ let add_lower_bound_ tpenv name ty =
191200
newable = false;
192201
require_dynamic = false;
193202
parameters = [];
203+
rank = 0;
194204
} )
195205
| Some (pos, tp) ->
196206
(pos, { tp with lower_bounds = TySet.add ty tp.lower_bounds })
@@ -236,6 +246,7 @@ let add_upper_bound ?intersect env_tpenv name ty =
236246
let newable = get_newable env_tpenv name in
237247
let require_dynamic = get_require_dynamic env_tpenv name in
238248
let parameters = [] in
249+
let rank = get_rank env_tpenv name in
239250
add
240251
~def_pos
241252
name
@@ -247,6 +258,7 @@ let add_upper_bound ?intersect env_tpenv name ty =
247258
newable;
248259
require_dynamic;
249260
parameters;
261+
rank;
250262
}
251263
tpenv
252264

@@ -280,6 +292,7 @@ let add_lower_bound ?union env_tpenv name ty =
280292
let newable = get_newable env_tpenv name in
281293
let require_dynamic = get_require_dynamic env_tpenv name in
282294
let parameters = [] in
295+
let rank = get_rank env_tpenv name in
283296
add
284297
~def_pos
285298
name
@@ -291,6 +304,7 @@ let add_lower_bound ?union env_tpenv name ty =
291304
newable;
292305
require_dynamic;
293306
parameters;
307+
rank;
294308
}
295309
tpenv
296310

@@ -358,6 +372,7 @@ let add_generic_parameters tpenv tparaml =
358372
newable;
359373
require_dynamic;
360374
parameters = nested_params;
375+
rank = 0;
361376
}
362377
in
363378

0 commit comments

Comments
 (0)