Skip to main content

hir_ty/infer/
place_op.rs

1//! Inference of *place operators*: deref and indexing (operators that create places, as opposed to values).
2
3use hir_def::hir::ExprId;
4use rustc_ast_ir::Mutability;
5use rustc_type_ir::inherent::{IntoKind, Ty as _};
6use tracing::debug;
7
8use crate::{
9    Adjust, Adjustment, AutoBorrow, PointerCast,
10    autoderef::InferenceContextAutoderef,
11    infer::{AllowTwoPhase, AutoBorrowMutability, InferenceContext, unify::InferenceTable},
12    method_resolution::{MethodCallee, TreatNotYetDefinedOpaques},
13    next_solver::{
14        ClauseKind, Ty, TyKind,
15        infer::{
16            InferOk,
17            traits::{Obligation, ObligationCause},
18        },
19    },
20};
21
22#[derive(Debug, Copy, Clone)]
23pub(super) enum PlaceOp {
24    Deref,
25    Index,
26}
27
28impl<'db> InferenceContext<'db> {
29    pub(super) fn try_overloaded_deref(
30        &self,
31        expr: ExprId,
32        base_ty: Ty<'db>,
33    ) -> Option<InferOk<'db, MethodCallee<'db>>> {
34        self.try_overloaded_place_op(expr, base_ty, None, PlaceOp::Deref)
35    }
36
37    /// For the overloaded place expressions (`*x`, `x[3]`), the trait
38    /// returns a type of `&T`, but the actual type we assign to the
39    /// *expression* is `T`. So this function just peels off the return
40    /// type by one layer to yield `T`.
41    fn make_overloaded_place_return_type(&self, method: MethodCallee<'db>) -> Ty<'db> {
42        // extract method return type, which will be &T;
43        let ret_ty = method.sig.output();
44
45        // method returns &T, but the type as visible to user is T, so deref
46        ret_ty.builtin_deref(true).unwrap()
47    }
48
49    /// Type-check `*oprnd_expr` with `oprnd_expr` type-checked already.
50    pub(super) fn lookup_derefing(
51        &mut self,
52        expr: ExprId,
53        oprnd_expr: ExprId,
54        oprnd_ty: Ty<'db>,
55    ) -> Option<Ty<'db>> {
56        if let Some(ty) = oprnd_ty.builtin_deref(true) {
57            return Some(ty);
58        }
59
60        let ok = self.try_overloaded_deref(expr, oprnd_ty)?;
61        let method = self.table.register_infer_ok(ok);
62        if let TyKind::Ref(_, _, Mutability::Not) = method.sig.inputs_and_output.inputs()[0].kind()
63        {
64            self.write_expr_adj(
65                oprnd_expr,
66                Box::new([Adjustment {
67                    kind: Adjust::Borrow(AutoBorrow::Ref(AutoBorrowMutability::Not)),
68                    target: method.sig.inputs_and_output.inputs()[0].store(),
69                }]),
70            );
71        } else {
72            panic!("input to deref is not a ref?");
73        }
74        let ty = self.make_overloaded_place_return_type(method);
75        self.write_method_resolution(expr, method.def_id, method.args);
76        Some(ty)
77    }
78
79    /// Type-check `*base_expr[index_expr]` with `base_expr` and `index_expr` type-checked already.
80    pub(super) fn lookup_indexing(
81        &mut self,
82        expr: ExprId,
83        base_expr: ExprId,
84        index_expr: ExprId,
85        base_ty: Ty<'db>,
86        idx_ty: Ty<'db>,
87    ) -> Option<(/*index type*/ Ty<'db>, /*element type*/ Ty<'db>)> {
88        // FIXME(#18741) -- this is almost but not quite the same as the
89        // autoderef that normal method probing does. They could likely be
90        // consolidated.
91
92        let mut autoderef =
93            InferenceContextAutoderef::new_from_inference_context(self, base_ty, base_expr.into());
94        let mut result = None;
95        while result.is_none() && autoderef.next().is_some() {
96            result = Self::try_index_step(expr, base_expr, index_expr, &mut autoderef, idx_ty);
97        }
98        result
99    }
100
101    /// To type-check `base_expr[index_expr]`, we progressively autoderef
102    /// (and otherwise adjust) `base_expr`, looking for a type which either
103    /// supports builtin indexing or overloaded indexing.
104    /// This loop implements one step in that search; the autoderef loop
105    /// is implemented by `lookup_indexing`.
106    fn try_index_step(
107        expr: ExprId,
108        base_expr: ExprId,
109        index_expr: ExprId,
110        autoderef: &mut InferenceContextAutoderef<'_, 'db>,
111        index_ty: Ty<'db>,
112    ) -> Option<(/*index type*/ Ty<'db>, /*element type*/ Ty<'db>)> {
113        let ty = autoderef.final_ty();
114        let adjusted_ty = autoderef.ctx().structurally_resolve_type(base_expr.into(), ty);
115        debug!(
116            "try_index_step(expr={:?}, base_expr={:?}, adjusted_ty={:?}, \
117             index_ty={:?})",
118            expr, base_expr, adjusted_ty, index_ty
119        );
120
121        for unsize in [false, true] {
122            let mut self_ty = adjusted_ty;
123            if unsize {
124                // We only unsize arrays here.
125                if let TyKind::Array(element_ty, ct) = adjusted_ty.kind() {
126                    let ctx = autoderef.ctx();
127                    ctx.table.register_predicate(Obligation::new(
128                        ctx.interner(),
129                        ObligationCause::new(base_expr),
130                        ctx.table.param_env,
131                        ClauseKind::ConstArgHasType(ct, ctx.types.types.usize),
132                    ));
133                    self_ty = Ty::new_slice(ctx.interner(), element_ty);
134                } else {
135                    continue;
136                }
137            }
138
139            // If some lookup succeeds, write callee into table and extract index/element
140            // type from the method signature.
141            // If some lookup succeeded, install method in table
142            let input_ty = autoderef.ctx().table.next_ty_var(index_expr.into());
143            let method = autoderef.ctx().try_overloaded_place_op(
144                expr,
145                self_ty,
146                Some(input_ty),
147                PlaceOp::Index,
148            );
149
150            if let Some(result) = method {
151                debug!("try_index_step: success, using overloaded indexing");
152                let method = autoderef.ctx().table.register_infer_ok(result);
153
154                let infer_ok = autoderef.adjust_steps_as_infer_ok();
155                let mut adjustments = autoderef.ctx().table.register_infer_ok(infer_ok);
156                if let TyKind::Ref(region, _, Mutability::Not) =
157                    method.sig.inputs_and_output.inputs()[0].kind()
158                {
159                    adjustments.push(Adjustment {
160                        kind: Adjust::Borrow(AutoBorrow::Ref(AutoBorrowMutability::Not)),
161                        target: Ty::new_imm_ref(autoderef.ctx().interner(), region, adjusted_ty)
162                            .store(),
163                    });
164                } else {
165                    panic!("input to index is not a ref?");
166                }
167                if unsize {
168                    adjustments.push(Adjustment {
169                        kind: Adjust::Pointer(PointerCast::Unsize),
170                        target: method.sig.inputs_and_output.inputs()[0].store(),
171                    });
172                }
173                autoderef.ctx().write_expr_adj(base_expr, adjustments.into_boxed_slice());
174
175                autoderef.ctx().write_method_resolution(expr, method.def_id, method.args);
176
177                return Some((input_ty, autoderef.ctx().make_overloaded_place_return_type(method)));
178            }
179        }
180
181        None
182    }
183
184    /// Try to resolve an overloaded place op. We only deal with the immutable
185    /// variant here (Deref/Index). In some contexts we would need the mutable
186    /// variant (DerefMut/IndexMut); those would be later converted by
187    /// `convert_place_derefs_to_mutable`.
188    pub(super) fn try_overloaded_place_op(
189        &self,
190        expr: ExprId,
191        base_ty: Ty<'db>,
192        opt_rhs_ty: Option<Ty<'db>>,
193        op: PlaceOp,
194    ) -> Option<InferOk<'db, MethodCallee<'db>>> {
195        debug!("try_overloaded_place_op({:?},{:?})", base_ty, op);
196
197        let (Some(imm_tr), Some(imm_op)) = (match op {
198            PlaceOp::Deref => (self.lang_items.Deref, self.lang_items.Deref_deref),
199            PlaceOp::Index => (self.lang_items.Index, self.lang_items.Index_index),
200        }) else {
201            // Bail if `Deref` or `Index` isn't defined.
202            return None;
203        };
204
205        // FIXME(trait-system-refactor-initiative#231): we may want to treat
206        // opaque types as rigid here to support `impl Deref<Target = impl Index<usize>>`.
207        let treat_opaques = TreatNotYetDefinedOpaques::AsInfer;
208        self.table.lookup_method_for_operator(
209            ObligationCause::new(expr),
210            imm_tr,
211            imm_op,
212            base_ty,
213            opt_rhs_ty,
214            treat_opaques,
215        )
216    }
217
218    pub(super) fn try_mutable_overloaded_place_op(
219        table: &InferenceTable<'db>,
220        expr: ExprId,
221        base_ty: Ty<'db>,
222        opt_rhs_ty: Option<Ty<'db>>,
223        op: PlaceOp,
224    ) -> Option<InferOk<'db, MethodCallee<'db>>> {
225        debug!("try_mutable_overloaded_place_op({:?},{:?})", base_ty, op);
226
227        let lang_items = table.interner().lang_items();
228        let (Some(mut_tr), Some(mut_op)) = (match op {
229            PlaceOp::Deref => (lang_items.DerefMut, lang_items.DerefMut_deref_mut),
230            PlaceOp::Index => (lang_items.IndexMut, lang_items.IndexMut_index_mut),
231        }) else {
232            // Bail if `DerefMut` or `IndexMut` isn't defined.
233            return None;
234        };
235
236        // We have to replace the operator with the mutable variant for the
237        // program to compile, so we don't really have a choice here and want
238        // to just try using `DerefMut` even if its not in the item bounds
239        // of the opaque.
240        let treat_opaques = TreatNotYetDefinedOpaques::AsInfer;
241        table.lookup_method_for_operator(
242            ObligationCause::new(expr),
243            mut_tr,
244            mut_op,
245            base_ty,
246            opt_rhs_ty,
247            treat_opaques,
248        )
249    }
250
251    pub(super) fn convert_place_op_to_mutable(
252        &mut self,
253        op: PlaceOp,
254        expr: ExprId,
255        base_expr: ExprId,
256        index_expr: Option<ExprId>,
257    ) {
258        debug!("convert_place_op_to_mutable({:?}, {:?}, {:?})", op, expr, base_expr);
259        if !self.result.method_resolutions.contains_key(&expr) {
260            debug!("convert_place_op_to_mutable - builtin, nothing to do");
261            return;
262        }
263
264        // Need to deref because overloaded place ops take self by-reference.
265        let base_ty = self
266            .expr_ty_after_adjustments(base_expr)
267            .builtin_deref(false)
268            .expect("place op takes something that is not a ref");
269
270        let arg_ty = match op {
271            PlaceOp::Deref => None,
272            PlaceOp::Index => {
273                // We would need to recover the `T` used when we resolve `<_ as Index<T>>::index`
274                // in try_index_step. This is the arg at index 1.
275                //
276                // FIXME: rustc does not use the type of `index_expr` with the following explanation.
277                //
278                // Note: we should *not* use `expr_ty` of index_expr here because autoderef
279                // during coercions can cause type of index_expr to differ from `T` (#72002).
280                // We also could not use `expr_ty_adjusted` of index_expr because reborrowing
281                // during coercions can also cause type of index_expr to differ from `T`,
282                // which can potentially cause regionck failure (#74933).
283                Some(self.expr_ty_after_adjustments(
284                    index_expr.expect("`PlaceOp::Index` should have `index_expr`"),
285                ))
286            }
287        };
288        let method = Self::try_mutable_overloaded_place_op(&self.table, expr, base_ty, arg_ty, op);
289        let method = match method {
290            Some(ok) => self.table.register_infer_ok(ok),
291            // Couldn't find the mutable variant of the place op, keep the
292            // current, immutable version.
293            None => return,
294        };
295        debug!("convert_place_op_to_mutable: method={:?}", method);
296        self.result.method_resolutions.insert(expr, (method.def_id, method.args.store()));
297
298        let TyKind::Ref(region, _, Mutability::Mut) =
299            method.sig.inputs_and_output.inputs()[0].kind()
300        else {
301            panic!("input to mutable place op is not a mut ref?");
302        };
303
304        // Convert the autoref in the base expr to mutable with the correct
305        // region and mutability.
306        let base_expr_ty = self.expr_ty(base_expr);
307        let interner = self.interner();
308        if let Some(adjustments) = self.result.expr_adjustments.get_mut(&base_expr) {
309            let mut source = base_expr_ty;
310            for adjustment in &mut adjustments[..] {
311                if let Adjust::Borrow(AutoBorrow::Ref(..)) = adjustment.kind {
312                    debug!("convert_place_op_to_mutable: converting autoref {:?}", adjustment);
313                    let mutbl = AutoBorrowMutability::Mut {
314                        // Deref/indexing can be desugared to a method call,
315                        // so maybe we could use two-phase here.
316                        // See the documentation of AllowTwoPhase for why that's
317                        // not the case today.
318                        allow_two_phase_borrow: AllowTwoPhase::No,
319                    };
320                    adjustment.kind = Adjust::Borrow(AutoBorrow::Ref(mutbl));
321                    adjustment.target = Ty::new_ref(interner, region, source, mutbl.into()).store();
322                }
323                source = adjustment.target.as_ref();
324            }
325
326            // If we have an autoref followed by unsizing at the end, fix the unsize target.
327            if let [
328                ..,
329                Adjustment { kind: Adjust::Borrow(AutoBorrow::Ref(..)), .. },
330                Adjustment { kind: Adjust::Pointer(PointerCast::Unsize), ref mut target },
331            ] = adjustments[..]
332            {
333                *target = method.sig.inputs_and_output.inputs()[0].store();
334            }
335        }
336    }
337}