@@ -28,8 +28,70 @@ module ShapeMap = Nast.ShapeMap
28
28
module ShapeSet = Ast_defs. ShapeSet
29
29
module Nast = Aast
30
30
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
+
31
90
type 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 ;
33
95
no_top_bottom : bool ;
34
96
treat_dynamic_as_bottom : bool ;
35
97
(* Used with global flag --enable-sound-dynamic-type, allow_subtype_of_dynamic
@@ -38,28 +100,21 @@ type subtype_env = {
38
100
on_error : Errors .typing_error_callback ;
39
101
}
40
102
41
- let empty_seen = Some SSet. empty
42
-
43
103
let make_subtype_env
44
- ?(seen_generic_params = empty_seen )
104
+ ?(ignore_generic_params = false )
45
105
?(no_top_bottom = false )
46
106
?(treat_dynamic_as_bottom = false )
47
107
?(allow_subtype_of_dynamic = false )
48
108
on_error =
49
109
{
50
- seen_generic_params;
110
+ ignore_generic_params;
111
+ visited = VisitedGoals. empty;
51
112
no_top_bottom;
52
113
treat_dynamic_as_bottom;
53
114
allow_subtype_of_dynamic;
54
115
on_error;
55
116
}
56
117
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
-
63
118
type reactivity_extra_info = {
64
119
method_info : (* method_name *) (string * (* is_static *) bool ) option ;
65
120
class_ty : phase_ty option ;
@@ -419,15 +474,21 @@ and default_subtype
419
474
| (_ , Tgeneric (name_sub , tyargs )) ->
420
475
(* TODO(T69551141) handle type arguments. right now, just passin tyargs to
421
476
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
425
480
(* If we've seen this type parameter before then we must have gone
426
481
* round a cycle so we fail
427
482
*)
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
431
492
(* If the generic is actually an expression dependent type,
432
493
we need to update this_ty
433
494
*)
@@ -440,7 +501,6 @@ and default_subtype
440
501
else
441
502
this_ty
442
503
in
443
- let subtype_env = add_seen_generic subtype_env name_sub in
444
504
(* Otherwise, we collect all the upper bounds ("as" constraints) on
445
505
the generic parameter, and check each of these in turn against
446
506
ty_super until one of them succeeds
@@ -479,7 +539,7 @@ and default_subtype
479
539
env
480
540
|> try_bounds
481
541
(Typing_set. elements
482
- (Env. get_upper_bounds env name_sub tyargs)))
542
+ (Env. get_upper_bounds env name_sub tyargs)) )
483
543
|> (* Turn error into a generic error about the type parameter *)
484
544
if_unsat (invalid ~fail )
485
545
| (_ , Tdynamic) when subtype_env.treat_dynamic_as_bottom -> valid env
@@ -536,7 +596,7 @@ and default_subtype
536
596
* C <: J ==> Unsat _
537
597
* (Assuming we have T <: D in tpenv, and class D implements I)
538
598
* 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
540
600
*)
541
601
and simplify_subtype_i
542
602
~(subtype_env : subtype_env )
@@ -935,7 +995,7 @@ and simplify_subtype_i
935
995
| LoclType lty_sub ->
936
996
(match deref lty_sub with
937
997
| (_ , (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 ->
939
999
default_subtype env
940
1000
(* Num is not atomic: it is equivalent to int|float. The rule below relies
941
1001
* on ty_sub not being a union e.g. consider num <: arraykey | float, so
@@ -1033,8 +1093,7 @@ and simplify_subtype_i
1033
1093
(* We do not want to decompose Toption for these cases *)
1034
1094
| ((_ , (Tvar _ | Tunion _ | Tintersection _ )), _ ) ->
1035
1095
default_subtype env
1036
- | ((_, Tgeneric _), _)
1037
- when Option. is_none subtype_env.seen_generic_params ->
1096
+ | ((_ , Tgeneric _ ), _ ) when subtype_env.ignore_generic_params ->
1038
1097
(* TODO(T69551141) handle type arguments ? *)
1039
1098
default_subtype env
1040
1099
(* If t1 <: ?t2 and t1 is an abstract type constrained as t1',
@@ -1164,16 +1223,21 @@ and simplify_subtype_i
1164
1223
| Terr ->
1165
1224
default_subtype env
1166
1225
| _ ->
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 (
1170
1229
(* If we've seen this type parameter before then we must have gone
1171
1230
* round a cycle so we fail
1172
1231
*)
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
1177
1241
(* Collect all the lower bounds ("super" constraints) on the
1178
1242
* generic parameter, and check ty_sub against each of them in turn
1179
1243
* until one of them succeeds *)
@@ -1190,7 +1254,8 @@ and simplify_subtype_i
1190
1254
|> try_bounds
1191
1255
(Typing_set. elements
1192
1256
(Env. get_lower_bounds env name_super tyargs_super))
1193
- |> if_unsat invalid_env)))
1257
+ |> if_unsat invalid_env
1258
+ )))
1194
1259
| (_ , Tnonnull) ->
1195
1260
(match ety_sub with
1196
1261
| ConstraintType cty ->
@@ -3308,17 +3373,12 @@ and is_sub_type_alt_i
3308
3373
let (_env, prop) =
3309
3374
simplify_subtype_i
3310
3375
~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))
3322
3382
~this_ty
3323
3383
(* It is weird that this can cause errors, but I am wary to discard them.
3324
3384
* Using the generic unify_error to maintain current behavior. *)
@@ -3611,7 +3671,7 @@ let rec decompose_subtype
3611
3671
ty_super;
3612
3672
let (env, prop) =
3613
3673
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)
3615
3675
~this_ty: None
3616
3676
ty_sub
3617
3677
ty_super
0 commit comments