1 // Copyright (c) Meta Platforms, Inc. and affiliates.
3 // This source code is licensed under the MIT license found in the
4 // LICENSE file in the "hack" directory of this source tree.
6 use super::{config::Config, env::Env, solve, visited_goals::GoalResult};
7 use crate::typing::typing_error::Result;
9 use itertools::{izip, Itertools};
10 use oxidized::ast_defs::Variance;
11 use pos::{Positioned, Symbol, TypeName};
14 local::{Exact, FunType, Prim, Ty, Ty_, Tyvar},
15 local_error::{Primary, TypingError},
20 /// Normalize the proposition `T <: U`
21 pub fn simp_ty<R: Reason>(
26 ) -> Result<Prop<R>> {
28 let r_sub = ty_sub.reason();
29 let ety_sup = env.inf_env.resolve_ty(ty_sup);
30 let ety_sub = env.inf_env.resolve_ty(ty_sub);
31 match ety_sup.deref() {
32 // -- Super is var -----------------------------------------------------
34 match ety_sub.deref() {
35 Tunion(_) => simp_ty_common(cfg, env, &ety_sub, &ety_sup),
36 Toption(ty_sub_inner) => {
37 let ty_sub_inner = env.inf_env.resolve_ty(ty_sub_inner);
38 // We special case on `mixed <: Tvar _`, adding the entire `mixed` type
39 // as a lower bound. This enables clearer error messages when upper bounds
40 // are added to the type variable: transitive closure picks up the
41 // entire `mixed` type, and not separately consider `null` and `nonnull`
42 if ty_sub_inner.is_nonnull() {
43 Ok(Prop::subtype_ty(ety_sub, ety_sup))
45 let ty_null = Ty::null(r_sub.clone());
46 simp_conj_sub(cfg, env, &ty_sub_inner, &ty_null, &ety_sup)
49 Tvar(tv_sub) if tv_sub == tv_sup => Ok(Prop::valid()),
50 _ => Ok(Prop::subtype_ty(ety_sub, ety_sup)),
54 // -- Super is union ---------------------------------------------------
56 // TOOD[mjt] we don't have negation yet so we're dropping special
57 // case code for t <: (#1 | arraykey) here
58 match ety_sub.deref() {
59 Tunion(_) | Tvar(_) => simp_ty_common(cfg, env, &ety_sub, &ety_sup),
60 Tgeneric(_, _) if cfg.ignore_generic_params => {
61 simp_ty_common(cfg, env, &ety_sub, &ety_sup)
63 Tprim(p) if matches!(p, Prim::Tnum) => {
64 let r = ety_sub.reason();
65 let ty_float = Ty::float(r.clone());
66 let ty_int = Ty::int(r.clone());
67 simp_conj_sub(cfg, env, &ty_float, &ty_int, ty_sup)
69 Toption(ty_sub_inner) => {
70 let r = ety_sub.reason();
71 let ty_null = Ty::null(r.clone());
72 let p = simp_conj_sub(cfg, env, ty_sub_inner, &ty_null, ty_sup)?;
74 Ok(Prop::invalid(TypingError::Primary(Primary::Subtype)))
80 // TODO[mjt] this omits all the like-type pushing for
82 let p1 = simp_disjs_sup(cfg, env, &ety_sub, ty_sups.iter())?;
83 let p2 = match ety_sub.deref() {
84 // TODO[mjt] It's this kind of thing which makes
85 // understanding subtyping unnecessarily difficult; here
86 // we are matching on the subtype 3 times(!) - twice
87 // here and once in `simp_ty_common` when we really
88 // should pull the generic subtyping logic out instead
89 // and leave this match one level up.
90 // We should go through this and simplify the nested
92 Tgeneric(_, _) => simp_ty_common(cfg, env, &ety_sub, ty_sup)?,
93 _ => Prop::invalid(TypingError::Primary(Primary::Subtype)),
95 Ok(p1.disj(p2, TypingError::Primary(Primary::Subtype)))
100 // -- Super is option --------------------------------------------------
101 Toption(ty_sup_inner) => {
102 let ety_sup_inner = env.inf_env.resolve_ty(ty_sup_inner);
103 // ?nonnull === mixed
104 // TODO[mjt] why do we use this encoding?
105 if ety_sup_inner.is_nonnull() {
108 match ety_sub.deref() {
109 Tprim(p_sub) => match p_sub {
110 Prim::Tnull => Ok(Prop::valid()),
112 let p1 = simp_ty(cfg, env, &ety_sub, &ety_sup_inner)?;
116 // TODO[mjt]: check on this rule! Here `simp_ty_common`
117 // is `?nonnull == mixed <: ety_sup` (as an implicit upper bound ?)
118 // but we already know that ty_sup is _not_ top, don't we?
119 let p2 = simp_ty_common(cfg, env, &ety_sub, &ety_sup)?;
120 Ok(p1.disj(p2, TypingError::Primary(Primary::Subtype)))
123 _ => simp_ty(cfg, env, &ety_sub, &ety_sup_inner),
125 // ?t <: ?u iff t < ?u
127 // Since t <: ?t and the transitivity of <: we have t <: ?u
129 // If t <: ?u by covariance of ? we have ?t <: ?u and
130 // by idempotence we have ?t <: ??t
131 // so this step preserves the set of solutions
132 Toption(ty_sub_inner) => simp_ty(cfg, env, ty_sub_inner, &ety_sup),
133 // TODO[mjt] Again, this needlessly repeats pattern matching
134 // - lets pull the Tvar case out from `simp_ty_common`
135 Tvar(_) | Tunion(_) => simp_ty_common(cfg, env, &ety_sub, &ety_sup),
136 Tgeneric(_, _) if cfg.ignore_generic_params => {
137 simp_ty_common(cfg, env, &ety_sub, &ety_sup)
139 Tgeneric(_, _) => simp_disj_sup(cfg, env, &ety_sub, &ety_sup_inner, &ety_sup),
140 // All of these cannot we have t </: null so we have
142 Tnonnull | Tfun(_) | Tclass(_, _, _) => {
143 simp_ty(cfg, env, &ety_sub, &ety_sup_inner)
146 unimplemented!("Subtype propositions involving `Tany` aren't implemented")
151 // -- Super is generic -------------------------------------------------
152 Tgeneric(tp_name_sup, _) => match ety_sub.deref() {
153 // If subtype and supertype are the same generic parameter, we're done
154 Tgeneric(tp_name_sub, _) if tp_name_sub == tp_name_sup => Ok(Prop::valid()),
155 Tvar(_) | Tunion(_) => simp_ty_common(cfg, env, &ety_sub, &ety_sup),
156 _ if cfg.ignore_generic_params => Ok(Prop::subtype_ty(ety_sub, ety_sup)),
157 _ => simp_ty_typaram(cfg, env, ety_sub, (ety_sup.reason(), tp_name_sup)),
160 // -- Super is nonnull -------------------------------------------------
161 Tnonnull => match ety_sub.deref() {
164 // TODO[mjt] test for nullable on prims?
166 Tnull | Tvoid => Ok(Prop::invalid(TypingError::Primary(Primary::Subtype))),
167 Tint | Tbool | Tfloat | Tstring | Tresource | Tnum | Tarraykey | Tnoreturn => {
172 Tnonnull | Tfun(_) | Tclass(_, _, _) => Ok(Prop::valid()),
173 Tvar(_) | Tgeneric(_, _) | Toption(_) | Tunion(_) => {
174 simp_ty_common(cfg, env, &ety_sub, &ety_sup)
176 Tany => unimplemented!("Subtype propositions involving `Tany` aren't implemented"),
179 // -- Super is prim ----------------------------------------------------
180 Tprim(p_sup) => match ety_sub.deref() {
181 Tprim(p_sub) => Ok(simp_prim(p_sub, p_sup)),
182 Toption(ty_sub_inner) if p_sup.is_tnull() => simp_ty(cfg, env, ty_sub_inner, &ety_sup),
189 | Tunion(_) => simp_ty_common(cfg, env, &ety_sub, &ety_sup),
190 Tany => unimplemented!("Subtype propositions involving `Tany` aren't implemented"),
193 // -- Super is fun -----------------------------------------------------
194 Tfun(fn_sup) => match ety_sub.deref() {
195 Tfun(fn_sub) => simp_fun(cfg, env, fn_sub, fn_sup),
202 | Tunion(_) => simp_ty_common(cfg, env, &ety_sub, &ety_sup),
203 Tany => unimplemented!("Subtype propositions involving `Tany` aren't implemented"),
206 // -- Super is class ---------------------------------------------------
207 Tclass(cn_sup, exact_sup, tp_sups) => {
208 match ety_sub.deref() {
209 Tclass(cn_sub, exact_sub, tp_subs) => simp_class(
210 cfg, env, cn_sub, exact_sub, tp_subs, cn_sup, exact_sup, tp_sups,
212 // TODO[mjt] make a call on whether we bring
213 // string <: Stringish
214 // string , arraykey ,int, float, num <: XHPChild
215 // special-cases across
216 Tprim(_) => Ok(Prop::invalid(TypingError::Primary(Primary::Subtype))),
217 Tfun(_) | Toption(_) | Tnonnull | Tvar(_) | Tgeneric(_, _) | Tunion(_) => {
218 simp_ty_common(cfg, env, &ety_sub, &ety_sup)
220 Tany => unimplemented!("Subtype propositions involving `Tany` aren't implemented"),
224 // -- Tany should not be in subtyping ----------------------------------
225 Tany => unimplemented!("Subtype propositions involving `Tany` aren't implemented"),
229 fn simp_ty_common<R: Reason>(
234 ) -> Result<Prop<R>> {
236 let ty_sub = env.inf_env.resolve_ty(ty_sub);
237 let ty_sup = env.inf_env.resolve_ty(ty_sup);
238 match ty_sub.deref() {
239 Tvar(tv) => Ok(simp_tvar_ty(cfg, env, ty_sub.reason(), tv, &ty_sup)),
241 if matches!(p_sub, Prim::Tvoid) {
242 // TODO[mjt]: implicit upper bound reason
244 R::implicit_upper_bound(ty_sub.reason().pos().clone(), Symbol::new("?nonnull"));
245 let ty_mixed = Ty::mixed(reason);
246 simp_ty(cfg, env, &ty_mixed, &ty_sup)
248 Ok(Prop::invalid(TypingError::Primary(Primary::Subtype)))
251 Tunion(ty_subs) => simp_conjs_sub(cfg, env, ty_subs, &ty_sup),
252 // TODO[mjt] we aren't getting anywhere near HKTs so we should tidy up Tgeneric
253 Tgeneric(_, _) if cfg.ignore_generic_params => Ok(Prop::subtype_ty(ty_sub, ty_sup)),
254 Tgeneric(tp_name_sub, _) => {
255 simp_typaram_ty(cfg, env, (ty_sub.reason(), tp_name_sub), ty_sup)
257 Tclass(_, _, _) | Toption(_) | Tnonnull | Tfun(_) | Tany => {
258 Ok(Prop::invalid(TypingError::Primary(Primary::Subtype)))
263 /// Normalize the proposition `#1 <: T`
264 fn simp_tvar_ty<R: Reason>(
271 let ty_sup = solve::remove_tyvar_from_upper_bound(&mut env.inf_env, *tv_sub, ty_sup);
272 if env.inf_env.is_mixed(&ty_sup) {
273 // The tyvar occurred in the super type and removing it resulted in
274 // ty_sup collapsing to top
277 let ty_sub = Ty::var(r_sub.clone(), *tv_sub);
278 Prop::subtype_ty(ty_sub, ty_sup)
282 /// Normalize the proposition `#class<...> <: #class<...>`
283 fn simp_class<R: Reason>(
286 cn_sub: &Positioned<TypeName, R::Pos>,
289 cn_sup: &Positioned<TypeName, R::Pos>,
292 ) -> Result<Prop<R>> {
293 let exact_match = match (exact_sub, exact_sup) {
294 (Exact::Nonexact, Exact::Exact) => false,
297 if cn_sub.id() == cn_sup.id() {
298 if tp_subs.is_empty() && tp_sups.is_empty() && exact_match {
301 let class_def_sub = env.decl_env.get_class(cn_sub.id())?;
302 // If class is final then exactness is superfluous
303 let is_final = class_def_sub.clone().map_or(false, |cls| cls.is_final());
304 if !(exact_match || is_final) {
305 Ok(Prop::invalid(TypingError::Primary(Primary::Subtype)))
306 } else if tp_subs.len() != tp_sups.len() {
307 // TODO[mjt] these are different cases due to the errors
309 Ok(Prop::invalid(TypingError::Primary(Primary::Subtype)))
311 let variance_sub = if tp_subs.is_empty() {
314 class_def_sub.map_or_else(
315 || vec![Variance::Invariant; tp_subs.len()],
316 |cls| cls.get_tparams().iter().map(|t| (t.variance)).collect_vec(),
319 simp_conjs_with_variance(cfg, env, cn_sub, variance_sub, tp_subs, tp_sups)
322 } else if !exact_match {
323 // class in subtype position is non-exact and class is supertype position is exact
324 Ok(Prop::invalid(TypingError::Primary(Primary::Subtype)))
326 let class_sub_opt = env.decl_env.get_class(cn_sub.id())?;
327 if let Some(class_sub) = class_sub_opt {
328 // is our class is subtype positition a declared subtype of the
329 // class in supertype position
330 match class_sub.get_ancestor(&cn_sup.id()) {
332 // instantiate the declared type at the type parameters for
333 // the class in subtype position
334 // TODO[mjt] class localization
336 "Proposition normalization is not fully implemented for class types"
339 // None if class_sub.kind
340 None => Ok(Prop::invalid(TypingError::Primary(Primary::Subtype))),
343 // This should have been caught already, in the naming phase
344 // TODO[mjt] should we surface some of these implicit assumptions
351 fn simp_conjs_with_variance<R: Reason>(
354 cn: &Positioned<TypeName, R::Pos>,
355 variances: Vec<Variance>,
358 ) -> Result<Prop<R>> {
359 let mut prop = Prop::valid();
360 for (variance, ty_sub, ty_sup) in izip!(variances.iter(), ty_subs, ty_sups) {
361 let next = simp_with_variance(cfg, env, cn, variance, ty_sub, ty_sup)?;
366 prop = prop.conj(next);
372 fn simp_with_variance<R: Reason>(
375 _cn: &Positioned<TypeName, R::Pos>,
379 ) -> Result<Prop<R>> {
380 let mut this_ty = None;
381 std::mem::swap(&mut this_ty, &mut env.this_ty);
383 let p = match variance {
384 Variance::Covariant => simp_ty(cfg, env, ty_sub, ty_sup),
385 Variance::Contravariant =>
386 // TODO[mjt] update the reason on the supertype
388 simp_ty(cfg, env, ty_sup, ty_sub)
390 Variance::Invariant => {
391 // TODO[mjt] update the reason on the supertype
392 // TODO[mjt] if I use short-circuiting ? here will env be restored?
393 match simp_ty(cfg, env, ty_sub, ty_sup) {
394 Ok(p1) if !p1.is_unsat() => simp_ty(cfg, env, ty_sup, ty_sub).map(|p2| p1.conj(p2)),
399 std::mem::swap(&mut this_ty, &mut env.this_ty);
403 /// Normalize the proposition `prim <: prim`
404 /// TODO[mjt]: move to or wrap `subtype` test for prim impl?
405 fn simp_prim<R: Reason>(prim_sub: &Prim, prim_sup: &Prim) -> Prop<R> {
406 if prim_sub == prim_sup {
410 match (prim_sub, prim_sup) {
411 (Tint | Tfloat, Tnum) => Prop::valid(),
412 (Tint | Tstring, Tarraykey) => Prop::valid(),
413 _ => Prop::invalid(TypingError::Primary(Primary::Subtype)),
418 /// Normalize the propostion `fn <: fn`
419 fn simp_fun<R: Reason>(
422 _fn_sub: &FunType<R>,
423 _fn_sup: &FunType<R>,
424 ) -> Result<Prop<R>> {
425 unimplemented!("Subtype propositions involving function types aren't implemented")
428 /// Normalize the proposition `#T <: U` where `#T` is a generic type parameter
429 fn simp_typaram_ty<R: Reason>(
432 tp_sub: (&R, &TypeName),
434 ) -> Result<Prop<R>> {
437 .try_add_visited_generic_sub(tp_sub.1.clone(), &ty_sup)
439 // We've seen this type param before so must have gone round a
441 // TODO[mjt] do we want indicate failure here? Doesn't a cycle indicate
442 // something is wrong?
443 GoalResult::AlreadyVisited => Ok(Prop::invalid(TypingError::Primary(Primary::Subtype))),
445 // Otherwise, we collect all the upper bounds ("as" constraints) on
446 // the generic parameter, and check each of these in turn against
447 // ty_super until one of them succeeds
448 GoalResult::NewGoal => {
451 .upper_bounds(tp_sub.1)
452 .map_or(HashSet::default(), |bs| bs.clone());
453 let p = if bounds.is_empty() {
455 R::implicit_upper_bound(tp_sub.0.pos().clone(), Symbol::new("?nonnull"));
456 let ty_mixed = Ty::mixed(reason);
457 simp_ty(cfg, env, &ty_mixed, &ty_sup)?
459 simp_disjs_sub(cfg, env, bounds.iter(), &ty_sup)?
461 // TODO[mjt] we check this here to get a nicer message, presumably?
463 Ok(Prop::invalid(TypingError::Primary(Primary::Subtype)))
471 /// Normalize the proposition `T <: #U` where `#U` is a generic type parameter
472 fn simp_ty_typaram<R: Reason>(
476 tp_sup: (&R, &TypeName),
477 ) -> Result<Prop<R>> {
480 .try_add_visited_generic_sup(tp_sup.1.clone(), &ty_sub)
482 // We've seen this type param before so must have gone round a
484 GoalResult::AlreadyVisited => Ok(Prop::invalid(TypingError::Primary(Primary::Subtype))),
486 // Collect all the lower bounds ("super" constraints) on the
487 // generic parameter, and check ty_sub against each of them in turn
488 // until one of them succeeds
489 GoalResult::NewGoal => {
492 .lower_bounds(tp_sup.1)
493 .map_or(HashSet::default(), |bs| bs.clone());
494 let prop = simp_disjs_sup(cfg, env, &ty_sub, bounds.iter())?;
498 let ty_sup = Ty::generic(tp_sup.0.clone(), tp_sup.1.clone(), vec![]);
500 Prop::subtype_ty(ty_sub, ty_sup),
501 TypingError::Primary(Primary::Subtype),
508 /// Normalize the propostion `T1 <: U /\ T2 <: U`
509 fn simp_conj_sub<R: Reason>(
515 ) -> Result<Prop<R>> {
516 let p1 = simp_ty(cfg, env, ty_sub1, ty_sup)?;
520 let p2 = simp_ty(cfg, env, ty_sub2, ty_sup)?;
525 /// Normalize the proposition `T <: U1 \/ T <: U2`
526 fn simp_disj_sup<R: Reason>(
532 ) -> Result<Prop<R>> {
533 let p1 = simp_ty(cfg, env, ty_sub, ty_sup1)?;
537 let p2 = simp_ty(cfg, env, ty_sub, ty_sup2)?;
542 /// Normalize the proposition `T1 <: U /\ T2 <: U /\ ... /\ Tn <: U`
543 fn simp_conjs_sub<'a, R, I>(
551 I: IntoIterator<Item = &'a Ty<R>>,
553 let mut prop = Prop::valid();
554 for ty_sub in ty_subs {
555 let next = simp_ty(cfg, env, ty_sub, ty_sup)?;
560 prop = prop.conj(next);
566 /// Normalize the proposition `T <: U1 \/ T <: U2`
567 fn simp_disjs_sup<'a, R, I>(
575 I: IntoIterator<Item = &'a Ty<R>>,
577 let mut prop = Prop::invalid(TypingError::Primary(Primary::Subtype));
578 for ty_sup in ty_sups {
579 let next = simp_ty(cfg, env, ty_sub, ty_sup)?;
584 prop = prop.disj(next, TypingError::Primary(Primary::Subtype));
590 /// Normalize the proposition `T <: U1 \/ T <: U2`
591 fn simp_disjs_sub<'a, R, I>(
599 I: IntoIterator<Item = &'a Ty<R>>,
601 let mut prop = Prop::invalid(TypingError::Primary(Primary::Subtype));
602 for ty_sub in ty_subs {
603 let next = simp_ty(cfg, env, ty_sub, ty_sup)?;
608 prop = prop.disj(next, TypingError::Primary(Primary::Subtype));
617 use ty::{prop::PropF, reason::NReason};
618 use utils::core::IdentGen;
622 let mut env = Env::default();
623 let cfg = Config::default();
624 let gen = IdentGen::new();
626 let tv_0: Tyvar = gen.make().into();
628 let also_tv_0: Tyvar = tv_0.clone();
629 let r_tv_0 = NReason::none();
630 let ty_v0 = Ty::var(NReason::none(), tv_0);
633 let p0 = simp_tvar_ty(&cfg, &mut env, &r_tv_0, &also_tv_0, &ty_v0);
634 assert!(p0.is_valid());
636 // #0 <: int => #0 <: int
637 let ty_int = Ty::int(NReason::none());
638 let p1 = simp_tvar_ty(&cfg, &mut env, &r_tv_0, &also_tv_0, &ty_int);
639 assert!(matches!(p1.deref(), PropF::Subtype(_, _)));
641 // #0 <: int | 0 => true
642 let ty_int_or_tv0 = Ty::union(NReason::none(), vec![ty_v0.clone(), ty_int.clone()]);
643 let p2 = simp_tvar_ty(&cfg, &mut env, &r_tv_0, &also_tv_0, &ty_int_or_tv0);
644 assert!(p2.is_valid());
646 // #0 <: (int | (int | 0)) => true
647 let ty_int_or_int_or_tv0 = Ty::union(NReason::none(), vec![ty_int.clone(), ty_int_or_tv0]);
648 let p3 = simp_tvar_ty(&cfg, &mut env, &r_tv_0, &also_tv_0, &ty_int_or_int_or_tv0);
649 assert!(p3.is_valid());
652 let ty_v0_opt = Ty::option(NReason::none(), ty_v0.clone());
653 let p4 = simp_tvar_ty(&cfg, &mut env, &r_tv_0, &also_tv_0, &ty_v0_opt);
654 assert!(p4.is_valid());
659 let mut env = Env::default();
660 let cfg = Config::default();
662 // Subtypes of arraykey
663 let ty_arraykey = Ty::arraykey(NReason::none());
665 let ty_int = Ty::int(NReason::none());
667 simp_ty(&cfg, &mut env, &ty_int, &ty_arraykey)
671 // string <: arraykey
672 let ty_string = Ty::string(NReason::none());
674 simp_ty(&cfg, &mut env, &ty_string, &ty_arraykey)
680 let ty_num = Ty::num(NReason::none());
683 simp_ty(&cfg, &mut env, &ty_int, &ty_num)
688 let ty_float = Ty::float(NReason::none());
690 simp_ty(&cfg, &mut env, &ty_float, &ty_num)
695 // num === (int | float)
696 let ty_num_as_union = Ty::union(NReason::none(), vec![ty_int.clone(), ty_float.clone()]);
698 simp_ty(&cfg, &mut env, &ty_num, &ty_num_as_union)
703 // Subtypes of nonnull
704 let ty_nonnull = Ty::nonnull(NReason::none());
707 simp_ty(&cfg, &mut env, &ty_int, &ty_nonnull)
713 simp_ty(&cfg, &mut env, &ty_int, &ty_nonnull)
719 simp_ty(&cfg, &mut env, &ty_int, &ty_nonnull)
724 let ty_null = Ty::null(NReason::none());
726 simp_ty(&cfg, &mut env, &ty_null, &ty_nonnull)
731 let ty_void = Ty::void(NReason::none());
733 simp_ty(&cfg, &mut env, &ty_void, &ty_nonnull)
740 fn test_disjs_sub() {
741 let mut env = Env::default();
742 let cfg = Config::default();
743 let ty_num = Ty::num(NReason::none());
744 let ty_subs_all_ok = vec![Ty::int(NReason::none()), Ty::float(NReason::none())];
745 let ty_subs_one_ok = vec![Ty::string(NReason::none()), Ty::float(NReason::none())];
746 let ty_subs_none_ok = vec![Ty::string(NReason::none()), Ty::null(NReason::none())];
748 simp_disjs_sub(&cfg, &mut env, ty_subs_all_ok.iter(), &ty_num)
753 simp_disjs_sub(&cfg, &mut env, ty_subs_one_ok.iter(), &ty_num)
758 simp_disjs_sub(&cfg, &mut env, ty_subs_none_ok.iter(), &ty_num)
765 fn test_ty_param_sup() {
766 let mut env = Env::default();
767 let cfg = Config::default();
768 let tp_name = TypeName::new("T");
769 let tp_reason = NReason::none();
770 let ty_int = Ty::int(NReason::none());
771 let ty_arraykey = Ty::arraykey(NReason::none());
773 env.tp_env.add_upper_bound(tp_name.clone(), ty_int);
774 // subtyping our typaram against arraykey will:
775 // add it to the visited supertypes
776 // check that we can subtype all existing upper bounds against it
777 // since our only upper bound is int, this is int <: arraykey
779 simp_typaram_ty(&cfg, &mut env, (&tp_reason, &tp_name), ty_arraykey)
786 fn test_ty_param_sub() {
787 let mut env = Env::default();
788 let cfg = Config::default();
789 let tp_name = TypeName::new("T");
790 let tp_reason = NReason::none();
791 let ty_int = Ty::int(NReason::none());
792 let ty_arraykey = Ty::arraykey(NReason::none());
794 env.tp_env.add_lower_bound(tp_name.clone(), ty_arraykey);
796 simp_ty_typaram(&cfg, &mut env, ty_int, (&tp_reason, &tp_name))