@@ -28,8 +28,70 @@ module ShapeMap = Nast.ShapeMap
2828module ShapeSet = Ast_defs. ShapeSet
2929module Nast = Aast
3030
31+ (* We maintain a "visited" set for subtype goals. We do this only
32+ * for goals of the form T <: t or t <: T where T is a generic parameter,
33+ * as this is the more common case.
34+ * T83096774: work out how to do this *efficiently* for all subtype goals.
35+ *
36+ * Here's a non-trivial example (assuming a contravariant type Contra).
37+ * Under assumption T <: Contra<Contra<T>> show T <: Contra<T>.
38+ * This leads to cycle of implications
39+ * T <: Contra<T> =>
40+ * Contra<Contra<T>> <: Contra<T> =>
41+ * T <: Contra<T>
42+ * at which point we are back at the original goal.
43+ *
44+ * Note that it's not enough to just keep a set of visited generic parameters,
45+ * else we would reject good code e.g. consider
46+ * class C extends B implements Contra<B>
47+ * Now under assumption T <: C show T <: Contra<T>
48+ * This leads to cycle of implications
49+ * T <: Contra<T> =>
50+ * C <: Contra<T> =>
51+ * Contra<B> <: Contra<T> =>
52+ * T <: B => // DO NOT REJECT here just because we've visited T before!
53+ * C <: B => done.
54+ *
55+ * We represent the visited set as a map from generic parameters
56+ * to pairs of sets of types, such that an entry T := ({t1,...,tm},{u1,...,un})
57+ * represents a set of goals
58+ * T <: u1, ..., t <: un , t1 <: T, ..., tn <: T
59+ *)
60+ module VisitedGoals = struct
61+ type t = (ITySet .t * ITySet .t ) SMap .t
62+
63+ let empty : t = SMap. empty
64+
65+ (* Return None if (name <: ty) is already present, otherwise return Some v'
66+ * where v' has the pair added
67+ *)
68+ let try_add_visited_generic_sub v name ty =
69+ match SMap. find_opt name v with
70+ | None -> Some (SMap. add name (ITySet. empty, ITySet. singleton ty) v)
71+ | Some (lower , upper ) ->
72+ if ITySet. mem ty upper then
73+ None
74+ else
75+ Some (SMap. add name (lower, ITySet. add ty upper) v)
76+
77+ (* Return None if (ty <: name) is already present, otherwise return Some v'
78+ * where v' has the pair added
79+ *)
80+ let try_add_visited_generic_super v ty name =
81+ match SMap. find_opt name v with
82+ | None -> Some (SMap. add name (ITySet. singleton ty, ITySet. empty) v)
83+ | Some (lower , upper ) ->
84+ if ITySet. mem ty lower then
85+ None
86+ else
87+ Some (SMap. add name (ITySet. add ty lower, upper) v)
88+ end
89+
3190type subtype_env = {
32- seen_generic_params : SSet .t option ;
91+ (* If set, finish as soon as we see a goal of the form T <: t or t <: T for generic parameter T *)
92+ ignore_generic_params : bool ;
93+ (* If above is not set, maintain a visited goal set *)
94+ visited : VisitedGoals .t ;
3395 no_top_bottom : bool ;
3496 treat_dynamic_as_bottom : bool ;
3597 (* Used with global flag --enable-sound-dynamic-type, allow_subtype_of_dynamic
@@ -38,28 +100,21 @@ type subtype_env = {
38100 on_error : Errors .typing_error_callback ;
39101}
40102
41- let empty_seen = Some SSet. empty
42-
43103let make_subtype_env
44- ?(seen_generic_params = empty_seen )
104+ ?(ignore_generic_params = false )
45105 ?(no_top_bottom = false )
46106 ?(treat_dynamic_as_bottom = false )
47107 ?(allow_subtype_of_dynamic = false )
48108 on_error =
49109 {
50- seen_generic_params;
110+ ignore_generic_params;
111+ visited = VisitedGoals. empty;
51112 no_top_bottom;
52113 treat_dynamic_as_bottom;
53114 allow_subtype_of_dynamic;
54115 on_error;
55116 }
56117
57- let add_seen_generic subtype_env name =
58- match subtype_env.seen_generic_params with
59- | Some seen ->
60- { subtype_env with seen_generic_params = Some (SSet. add name seen) }
61- | None -> subtype_env
62-
63118type reactivity_extra_info = {
64119 method_info : (* method_name *) (string * (* is_static *) bool ) option ;
65120 class_ty : phase_ty option ;
@@ -419,15 +474,21 @@ and default_subtype
419474 | (_ , Tgeneric (name_sub , tyargs )) ->
420475 (* TODO(T69551141) handle type arguments. right now, just passin tyargs to
421476 Env.get_upper_bounds *)
422- (match subtype_env.seen_generic_params with
423- | None -> default env
424- | Some seen ->
477+ ( if subtype_env.ignore_generic_params then
478+ default env
479+ else
425480 (* If we've seen this type parameter before then we must have gone
426481 * round a cycle so we fail
427482 *)
428- if SSet. mem name_sub seen then
429- invalid ~fail env
430- else
483+ match
484+ VisitedGoals. try_add_visited_generic_sub
485+ subtype_env.visited
486+ name_sub
487+ ty_super
488+ with
489+ | None -> invalid ~fail env
490+ | Some new_visited ->
491+ let subtype_env = { subtype_env with visited = new_visited } in
431492 (* If the generic is actually an expression dependent type,
432493 we need to update this_ty
433494 *)
@@ -440,7 +501,6 @@ and default_subtype
440501 else
441502 this_ty
442503 in
443- let subtype_env = add_seen_generic subtype_env name_sub in
444504 (* Otherwise, we collect all the upper bounds ("as" constraints) on
445505 the generic parameter, and check each of these in turn against
446506 ty_super until one of them succeeds
@@ -479,7 +539,7 @@ and default_subtype
479539 env
480540 |> try_bounds
481541 (Typing_set. elements
482- (Env. get_upper_bounds env name_sub tyargs)))
542+ (Env. get_upper_bounds env name_sub tyargs)) )
483543 |> (* Turn error into a generic error about the type parameter *)
484544 if_unsat (invalid ~fail )
485545 | (_ , Tdynamic) when subtype_env.treat_dynamic_as_bottom -> valid env
@@ -536,7 +596,7 @@ and default_subtype
536596 * C <: J ==> Unsat _
537597 * (Assuming we have T <: D in tpenv, and class D implements I)
538598 * vec<T> <: vec<I> ==> True
539- * This last one would be left as T <: I if seen_generic_params is None
599+ * This last one would be left as T <: I if subtype_env.ignore_generic_params=true
540600 *)
541601and simplify_subtype_i
542602 ~(subtype_env : subtype_env )
@@ -935,7 +995,7 @@ and simplify_subtype_i
935995 | LoclType lty_sub ->
936996 (match deref lty_sub with
937997 | (_ , (Tunion _ | Terr | Tvar _ )) -> default_subtype env
938- | (_ , Tgeneric _ ) when Option. is_none subtype_env.seen_generic_params ->
998+ | (_ , Tgeneric _ ) when subtype_env.ignore_generic_params ->
939999 default_subtype env
9401000 (* Num is not atomic: it is equivalent to int|float. The rule below relies
9411001 * on ty_sub not being a union e.g. consider num <: arraykey | float, so
@@ -1033,8 +1093,7 @@ and simplify_subtype_i
10331093 (* We do not want to decompose Toption for these cases *)
10341094 | ((_ , (Tvar _ | Tunion _ | Tintersection _ )), _ ) ->
10351095 default_subtype env
1036- | ((_, Tgeneric _), _)
1037- when Option. is_none subtype_env.seen_generic_params ->
1096+ | ((_ , Tgeneric _ ), _ ) when subtype_env.ignore_generic_params ->
10381097 (* TODO(T69551141) handle type arguments ? *)
10391098 default_subtype env
10401099 (* If t1 <: ?t2 and t1 is an abstract type constrained as t1',
@@ -1164,16 +1223,21 @@ and simplify_subtype_i
11641223 | Terr ->
11651224 default_subtype env
11661225 | _ ->
1167- ( match subtype_env.seen_generic_params with
1168- | None -> default env
1169- | Some seen ->
1226+ if subtype_env.ignore_generic_params then
1227+ default env
1228+ else (
11701229 (* If we've seen this type parameter before then we must have gone
11711230 * round a cycle so we fail
11721231 *)
1173- if SSet. mem name_super seen then
1174- invalid_env env
1175- else
1176- let subtype_env = add_seen_generic subtype_env name_super in
1232+ match
1233+ VisitedGoals. try_add_visited_generic_super
1234+ subtype_env.visited
1235+ ety_sub
1236+ name_super
1237+ with
1238+ | None -> invalid_env env
1239+ | Some new_visited ->
1240+ let subtype_env = { subtype_env with visited = new_visited } in
11771241 (* Collect all the lower bounds ("super" constraints) on the
11781242 * generic parameter, and check ty_sub against each of them in turn
11791243 * until one of them succeeds *)
@@ -1190,7 +1254,8 @@ and simplify_subtype_i
11901254 |> try_bounds
11911255 (Typing_set. elements
11921256 (Env. get_lower_bounds env name_super tyargs_super))
1193- |> if_unsat invalid_env)))
1257+ |> if_unsat invalid_env
1258+ )))
11941259 | (_ , Tnonnull) ->
11951260 (match ety_sub with
11961261 | ConstraintType cty ->
@@ -3308,17 +3373,12 @@ and is_sub_type_alt_i
33083373 let (_env, prop) =
33093374 simplify_subtype_i
33103375 ~subtype_env:
3311- {
3312- seen_generic_params =
3313- ( if ignore_generic_params then
3314- None
3315- else
3316- empty_seen );
3317- no_top_bottom;
3318- treat_dynamic_as_bottom;
3319- allow_subtype_of_dynamic;
3320- on_error = Errors. unify_error_at pos;
3321- }
3376+ (make_subtype_env
3377+ ~ignore_generic_params
3378+ ~no_top_bottom
3379+ ~treat_dynamic_as_bottom
3380+ ~allow_subtype_of_dynamic
3381+ (Errors. unify_error_at pos))
33223382 ~this_ty
33233383 (* It is weird that this can cause errors, but I am wary to discard them.
33243384 * Using the generic unify_error to maintain current behavior. *)
@@ -3611,7 +3671,7 @@ let rec decompose_subtype
36113671 ty_super;
36123672 let (env, prop) =
36133673 simplify_subtype
3614- ~subtype_env: (make_subtype_env ~seen_generic_params: None on_error)
3674+ ~subtype_env: (make_subtype_env ~ignore_generic_params: true on_error)
36153675 ~this_ty: None
36163676 ty_sub
36173677 ty_super
0 commit comments