rustc_infer/infer/outlives/
test_type_match.rs

1use std::collections::hash_map::Entry;
2
3use rustc_data_structures::fx::FxHashMap;
4use rustc_middle::ty::error::TypeError;
5use rustc_middle::ty::{self, Ty, TyCtxt, TypeVisitableExt};
6use tracing::instrument;
7
8use crate::infer::region_constraints::VerifyIfEq;
9use crate::infer::relate::{self as relate, Relate, RelateResult, TypeRelation};
10
11/// Given a "verify-if-eq" type test like:
12///
13/// ```rust,ignore (pseudo-Rust)
14/// exists<'a...> {
15///     verify_if_eq(some_type, bound_region)
16/// }
17/// ```
18///
19/// and the type `test_ty` that the type test is being tested against,
20/// returns:
21///
22/// * `None` if `some_type` cannot be made equal to `test_ty`,
23///   no matter the values of the variables in `exists`.
24/// * `Some(r)` with a suitable bound (typically the value of `bound_region`, modulo
25///   any bound existential variables, which will be instantiated) for the
26///   type under test.
27///
28/// NB: This function uses a simplistic, syntactic version of type equality.
29/// In other words, it may spuriously return `None` even if the type-under-test
30/// is in fact equal to `some_type`. In practice, though, this is used on types
31/// that are either projections like `T::Item` or `T` and it works fine, but it
32/// could have trouble when complex types with higher-ranked binders and the
33/// like are used. This is a particular challenge since this function is invoked
34/// very late in inference and hence cannot make use of the normal inference
35/// machinery.
36#[instrument(level = "debug", skip(tcx))]
37pub fn extract_verify_if_eq<'tcx>(
38    tcx: TyCtxt<'tcx>,
39    verify_if_eq_b: &ty::Binder<'tcx, VerifyIfEq<'tcx>>,
40    test_ty: Ty<'tcx>,
41) -> Option<ty::Region<'tcx>> {
42    assert!(!verify_if_eq_b.has_escaping_bound_vars());
43    let mut m = MatchAgainstHigherRankedOutlives::new(tcx);
44    let verify_if_eq = verify_if_eq_b.skip_binder();
45    m.relate(verify_if_eq.ty, test_ty).ok()?;
46
47    if let ty::RegionKind::ReBound(depth, br) = verify_if_eq.bound.kind() {
48        assert!(depth == ty::INNERMOST);
49        match m.map.get(&br) {
50            Some(&r) => Some(r),
51            None => {
52                // If there is no mapping, then this region is unconstrained.
53                // In that case, we escalate to `'static`.
54                Some(tcx.lifetimes.re_static)
55            }
56        }
57    } else {
58        // The region does not contain any bound variables, so we don't need
59        // to do any instantiation.
60        //
61        // Example:
62        //
63        // for<'a> <T as Foo<'a>>::Item: 'b
64        //
65        // In this case, we've now matched and found a value for
66        // `'a`, but it doesn't affect the bound `'b`.
67        Some(verify_if_eq.bound)
68    }
69}
70
71/// True if a (potentially higher-ranked) outlives
72#[instrument(level = "debug", skip(tcx))]
73pub(super) fn can_match_erased_ty<'tcx>(
74    tcx: TyCtxt<'tcx>,
75    outlives_predicate: ty::Binder<'tcx, ty::TypeOutlivesPredicate<'tcx>>,
76    erased_ty: Ty<'tcx>,
77) -> bool {
78    assert!(!outlives_predicate.has_escaping_bound_vars());
79    let erased_outlives_predicate = tcx.erase_regions(outlives_predicate);
80    let outlives_ty = erased_outlives_predicate.skip_binder().0;
81    if outlives_ty == erased_ty {
82        // pointless micro-optimization
83        true
84    } else {
85        MatchAgainstHigherRankedOutlives::new(tcx).relate(outlives_ty, erased_ty).is_ok()
86    }
87}
88
89struct MatchAgainstHigherRankedOutlives<'tcx> {
90    tcx: TyCtxt<'tcx>,
91    pattern_depth: ty::DebruijnIndex,
92    map: FxHashMap<ty::BoundRegion, ty::Region<'tcx>>,
93}
94
95impl<'tcx> MatchAgainstHigherRankedOutlives<'tcx> {
96    fn new(tcx: TyCtxt<'tcx>) -> MatchAgainstHigherRankedOutlives<'tcx> {
97        MatchAgainstHigherRankedOutlives {
98            tcx,
99            pattern_depth: ty::INNERMOST,
100            map: FxHashMap::default(),
101        }
102    }
103}
104
105impl<'tcx> MatchAgainstHigherRankedOutlives<'tcx> {
106    /// Creates the "Error" variant that signals "no match".
107    fn no_match<T>(&self) -> RelateResult<'tcx, T> {
108        Err(TypeError::Mismatch)
109    }
110
111    /// Binds the pattern variable `br` to `value`; returns an `Err` if the pattern
112    /// is already bound to a different value.
113    #[instrument(level = "trace", skip(self))]
114    fn bind(
115        &mut self,
116        br: ty::BoundRegion,
117        value: ty::Region<'tcx>,
118    ) -> RelateResult<'tcx, ty::Region<'tcx>> {
119        match self.map.entry(br) {
120            Entry::Occupied(entry) => {
121                if *entry.get() == value {
122                    Ok(value)
123                } else {
124                    self.no_match()
125                }
126            }
127            Entry::Vacant(entry) => {
128                entry.insert(value);
129                Ok(value)
130            }
131        }
132    }
133}
134
135impl<'tcx> TypeRelation<TyCtxt<'tcx>> for MatchAgainstHigherRankedOutlives<'tcx> {
136    fn cx(&self) -> TyCtxt<'tcx> {
137        self.tcx
138    }
139
140    #[instrument(level = "trace", skip(self))]
141    fn relate_with_variance<T: Relate<TyCtxt<'tcx>>>(
142        &mut self,
143        variance: ty::Variance,
144        _: ty::VarianceDiagInfo<TyCtxt<'tcx>>,
145        a: T,
146        b: T,
147    ) -> RelateResult<'tcx, T> {
148        // Opaque types args have lifetime parameters.
149        // We must not check them to be equal, as we never insert anything to make them so.
150        if variance != ty::Bivariant { self.relate(a, b) } else { Ok(a) }
151    }
152
153    #[instrument(skip(self), level = "trace")]
154    fn regions(
155        &mut self,
156        pattern: ty::Region<'tcx>,
157        value: ty::Region<'tcx>,
158    ) -> RelateResult<'tcx, ty::Region<'tcx>> {
159        if let ty::RegionKind::ReBound(depth, br) = pattern.kind()
160            && depth == self.pattern_depth
161        {
162            self.bind(br, value)
163        } else if pattern == value {
164            Ok(pattern)
165        } else {
166            self.no_match()
167        }
168    }
169
170    #[instrument(skip(self), level = "trace")]
171    fn tys(&mut self, pattern: Ty<'tcx>, value: Ty<'tcx>) -> RelateResult<'tcx, Ty<'tcx>> {
172        // FIXME(non_lifetime_binders): What to do here?
173        if matches!(pattern.kind(), ty::Error(_) | ty::Bound(..)) {
174            // Unlike normal `TypeRelation` rules, `ty::Error` does not equal any type.
175            self.no_match()
176        } else if pattern == value {
177            Ok(pattern)
178        } else {
179            relate::structurally_relate_tys(self, pattern, value)
180        }
181    }
182
183    #[instrument(skip(self), level = "trace")]
184    fn consts(
185        &mut self,
186        pattern: ty::Const<'tcx>,
187        value: ty::Const<'tcx>,
188    ) -> RelateResult<'tcx, ty::Const<'tcx>> {
189        if pattern == value {
190            Ok(pattern)
191        } else {
192            relate::structurally_relate_consts(self, pattern, value)
193        }
194    }
195
196    #[instrument(skip(self), level = "trace")]
197    fn binders<T>(
198        &mut self,
199        pattern: ty::Binder<'tcx, T>,
200        value: ty::Binder<'tcx, T>,
201    ) -> RelateResult<'tcx, ty::Binder<'tcx, T>>
202    where
203        T: Relate<TyCtxt<'tcx>>,
204    {
205        self.pattern_depth.shift_in(1);
206        let result = Ok(pattern.rebind(self.relate(pattern.skip_binder(), value.skip_binder())?));
207        self.pattern_depth.shift_out(1);
208        result
209    }
210}