]> git.lizzy.rs Git - rust.git/blob - compiler/rustc_hir_typeck/src/place_op.rs
Clean up operator type checking
[rust.git] / compiler / rustc_hir_typeck / src / place_op.rs
1 use crate::method::MethodCallee;
2 use crate::{has_expected_num_generic_args, FnCtxt, PlaceOp};
3 use rustc_ast as ast;
4 use rustc_errors::Applicability;
5 use rustc_hir as hir;
6 use rustc_infer::infer::type_variable::{TypeVariableOrigin, TypeVariableOriginKind};
7 use rustc_infer::infer::InferOk;
8 use rustc_middle::ty::adjustment::{Adjust, Adjustment, OverloadedDeref, PointerCast};
9 use rustc_middle::ty::adjustment::{AllowTwoPhase, AutoBorrow, AutoBorrowMutability};
10 use rustc_middle::ty::{self, Ty};
11 use rustc_span::symbol::{sym, Ident};
12 use rustc_span::Span;
13 use rustc_trait_selection::autoderef::Autoderef;
14 use std::slice;
15
16 impl<'a, 'tcx> FnCtxt<'a, 'tcx> {
17     /// Type-check `*oprnd_expr` with `oprnd_expr` type-checked already.
18     pub(super) fn lookup_derefing(
19         &self,
20         expr: &hir::Expr<'_>,
21         oprnd_expr: &'tcx hir::Expr<'tcx>,
22         oprnd_ty: Ty<'tcx>,
23     ) -> Option<Ty<'tcx>> {
24         if let Some(mt) = oprnd_ty.builtin_deref(true) {
25             return Some(mt.ty);
26         }
27
28         let ok = self.try_overloaded_deref(expr.span, oprnd_ty)?;
29         let method = self.register_infer_ok_obligations(ok);
30         if let ty::Ref(region, _, hir::Mutability::Not) = method.sig.inputs()[0].kind() {
31             self.apply_adjustments(
32                 oprnd_expr,
33                 vec![Adjustment {
34                     kind: Adjust::Borrow(AutoBorrow::Ref(*region, AutoBorrowMutability::Not)),
35                     target: method.sig.inputs()[0],
36                 }],
37             );
38         } else {
39             span_bug!(expr.span, "input to deref is not a ref?");
40         }
41         let ty = self.make_overloaded_place_return_type(method).ty;
42         self.write_method_call(expr.hir_id, method);
43         Some(ty)
44     }
45
46     /// Type-check `*base_expr[index_expr]` with `base_expr` and `index_expr` type-checked already.
47     pub(super) fn lookup_indexing(
48         &self,
49         expr: &hir::Expr<'_>,
50         base_expr: &'tcx hir::Expr<'tcx>,
51         base_ty: Ty<'tcx>,
52         index_expr: &'tcx hir::Expr<'tcx>,
53         idx_ty: Ty<'tcx>,
54     ) -> Option<(/*index type*/ Ty<'tcx>, /*element type*/ Ty<'tcx>)> {
55         // FIXME(#18741) -- this is almost but not quite the same as the
56         // autoderef that normal method probing does. They could likely be
57         // consolidated.
58
59         let mut autoderef = self.autoderef(base_expr.span, base_ty);
60         let mut result = None;
61         while result.is_none() && autoderef.next().is_some() {
62             result = self.try_index_step(expr, base_expr, &autoderef, idx_ty, index_expr);
63         }
64         self.register_predicates(autoderef.into_obligations());
65         result
66     }
67
68     fn negative_index(
69         &self,
70         ty: Ty<'tcx>,
71         span: Span,
72         base_expr: &hir::Expr<'_>,
73     ) -> Option<(Ty<'tcx>, Ty<'tcx>)> {
74         let ty = self.resolve_vars_if_possible(ty);
75         let mut err = self.tcx.sess.struct_span_err(
76             span,
77             &format!("negative integers cannot be used to index on a `{ty}`"),
78         );
79         err.span_label(span, &format!("cannot use a negative integer for indexing on `{ty}`"));
80         if let (hir::ExprKind::Path(..), Ok(snippet)) =
81             (&base_expr.kind, self.tcx.sess.source_map().span_to_snippet(base_expr.span))
82         {
83             // `foo[-1]` to `foo[foo.len() - 1]`
84             err.span_suggestion_verbose(
85                 span.shrink_to_lo(),
86                 &format!(
87                     "to access an element starting from the end of the `{ty}`, compute the index",
88                 ),
89                 format!("{snippet}.len() "),
90                 Applicability::MachineApplicable,
91             );
92         }
93         let reported = err.emit();
94         Some((
95             self.tcx.ty_error_with_guaranteed(reported),
96             self.tcx.ty_error_with_guaranteed(reported),
97         ))
98     }
99
100     /// To type-check `base_expr[index_expr]`, we progressively autoderef
101     /// (and otherwise adjust) `base_expr`, looking for a type which either
102     /// supports builtin indexing or overloaded indexing.
103     /// This loop implements one step in that search; the autoderef loop
104     /// is implemented by `lookup_indexing`.
105     fn try_index_step(
106         &self,
107         expr: &hir::Expr<'_>,
108         base_expr: &hir::Expr<'_>,
109         autoderef: &Autoderef<'a, 'tcx>,
110         index_ty: Ty<'tcx>,
111         index_expr: &hir::Expr<'_>,
112     ) -> Option<(/*index type*/ Ty<'tcx>, /*element type*/ Ty<'tcx>)> {
113         let adjusted_ty =
114             self.structurally_resolved_type(autoderef.span(), autoderef.final_ty(false));
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         if let hir::ExprKind::Unary(
122             hir::UnOp::Neg,
123             hir::Expr {
124                 kind: hir::ExprKind::Lit(hir::Lit { node: ast::LitKind::Int(..), .. }),
125                 ..
126             },
127         ) = index_expr.kind
128         {
129             match adjusted_ty.kind() {
130                 ty::Adt(def, _) if self.tcx.is_diagnostic_item(sym::Vec, def.did()) => {
131                     return self.negative_index(adjusted_ty, index_expr.span, base_expr);
132                 }
133                 ty::Slice(_) | ty::Array(_, _) => {
134                     return self.negative_index(adjusted_ty, index_expr.span, base_expr);
135                 }
136                 _ => {}
137             }
138         }
139
140         for unsize in [false, true] {
141             let mut self_ty = adjusted_ty;
142             if unsize {
143                 // We only unsize arrays here.
144                 if let ty::Array(element_ty, _) = adjusted_ty.kind() {
145                     self_ty = self.tcx.mk_slice(*element_ty);
146                 } else {
147                     continue;
148                 }
149             }
150
151             // If some lookup succeeds, write callee into table and extract index/element
152             // type from the method signature.
153             // If some lookup succeeded, install method in table
154             let input_ty = self.next_ty_var(TypeVariableOrigin {
155                 kind: TypeVariableOriginKind::AutoDeref,
156                 span: base_expr.span,
157             });
158             let method =
159                 self.try_overloaded_place_op(expr.span, self_ty, &[input_ty], PlaceOp::Index);
160
161             if let Some(result) = method {
162                 debug!("try_index_step: success, using overloaded indexing");
163                 let method = self.register_infer_ok_obligations(result);
164
165                 let mut adjustments = self.adjust_steps(autoderef);
166                 if let ty::Ref(region, _, hir::Mutability::Not) = method.sig.inputs()[0].kind() {
167                     adjustments.push(Adjustment {
168                         kind: Adjust::Borrow(AutoBorrow::Ref(*region, AutoBorrowMutability::Not)),
169                         target: self.tcx.mk_ref(
170                             *region,
171                             ty::TypeAndMut { mutbl: hir::Mutability::Not, ty: adjusted_ty },
172                         ),
173                     });
174                 } else {
175                     span_bug!(expr.span, "input to index is not a ref?");
176                 }
177                 if unsize {
178                     adjustments.push(Adjustment {
179                         kind: Adjust::Pointer(PointerCast::Unsize),
180                         target: method.sig.inputs()[0],
181                     });
182                 }
183                 self.apply_adjustments(base_expr, adjustments);
184
185                 self.write_method_call(expr.hir_id, method);
186
187                 return Some((input_ty, self.make_overloaded_place_return_type(method).ty));
188             }
189         }
190
191         None
192     }
193
194     /// Try to resolve an overloaded place op. We only deal with the immutable
195     /// variant here (Deref/Index). In some contexts we would need the mutable
196     /// variant (DerefMut/IndexMut); those would be later converted by
197     /// `convert_place_derefs_to_mutable`.
198     pub(super) fn try_overloaded_place_op(
199         &self,
200         span: Span,
201         base_ty: Ty<'tcx>,
202         arg_tys: &[Ty<'tcx>],
203         op: PlaceOp,
204     ) -> Option<InferOk<'tcx, MethodCallee<'tcx>>> {
205         debug!("try_overloaded_place_op({:?},{:?},{:?})", span, base_ty, op);
206
207         let (imm_tr, imm_op) = match op {
208             PlaceOp::Deref => (self.tcx.lang_items().deref_trait(), sym::deref),
209             PlaceOp::Index => (self.tcx.lang_items().index_trait(), sym::index),
210         };
211
212         // If the lang item was declared incorrectly, stop here so that we don't
213         // run into an ICE (#83893). The error is reported where the lang item is
214         // declared.
215         if !has_expected_num_generic_args(
216             self.tcx,
217             imm_tr,
218             match op {
219                 PlaceOp::Deref => 0,
220                 PlaceOp::Index => 1,
221             },
222         ) {
223             return None;
224         }
225
226         imm_tr.and_then(|trait_did| {
227             self.lookup_method_in_trait(
228                 span,
229                 Ident::with_dummy_span(imm_op),
230                 trait_did,
231                 base_ty,
232                 Some(arg_tys),
233             )
234         })
235     }
236
237     fn try_mutable_overloaded_place_op(
238         &self,
239         span: Span,
240         base_ty: Ty<'tcx>,
241         arg_tys: &[Ty<'tcx>],
242         op: PlaceOp,
243     ) -> Option<InferOk<'tcx, MethodCallee<'tcx>>> {
244         debug!("try_mutable_overloaded_place_op({:?},{:?},{:?})", span, base_ty, op);
245
246         let (mut_tr, mut_op) = match op {
247             PlaceOp::Deref => (self.tcx.lang_items().deref_mut_trait(), sym::deref_mut),
248             PlaceOp::Index => (self.tcx.lang_items().index_mut_trait(), sym::index_mut),
249         };
250
251         // If the lang item was declared incorrectly, stop here so that we don't
252         // run into an ICE (#83893). The error is reported where the lang item is
253         // declared.
254         if !has_expected_num_generic_args(
255             self.tcx,
256             mut_tr,
257             match op {
258                 PlaceOp::Deref => 0,
259                 PlaceOp::Index => 1,
260             },
261         ) {
262             return None;
263         }
264
265         mut_tr.and_then(|trait_did| {
266             self.lookup_method_in_trait(
267                 span,
268                 Ident::with_dummy_span(mut_op),
269                 trait_did,
270                 base_ty,
271                 Some(arg_tys),
272             )
273         })
274     }
275
276     /// Convert auto-derefs, indices, etc of an expression from `Deref` and `Index`
277     /// into `DerefMut` and `IndexMut` respectively.
278     ///
279     /// This is a second pass of typechecking derefs/indices. We need this because we do not
280     /// always know whether a place needs to be mutable or not in the first pass.
281     /// This happens whether there is an implicit mutable reborrow, e.g. when the type
282     /// is used as the receiver of a method call.
283     pub fn convert_place_derefs_to_mutable(&self, expr: &hir::Expr<'_>) {
284         // Gather up expressions we want to munge.
285         let mut exprs = vec![expr];
286
287         while let hir::ExprKind::Field(ref expr, _)
288         | hir::ExprKind::Index(ref expr, _)
289         | hir::ExprKind::Unary(hir::UnOp::Deref, ref expr) = exprs.last().unwrap().kind
290         {
291             exprs.push(expr);
292         }
293
294         debug!("convert_place_derefs_to_mutable: exprs={:?}", exprs);
295
296         // Fix up autoderefs and derefs.
297         let mut inside_union = false;
298         for (i, &expr) in exprs.iter().rev().enumerate() {
299             debug!("convert_place_derefs_to_mutable: i={} expr={:?}", i, expr);
300
301             let mut source = self.node_ty(expr.hir_id);
302             if matches!(expr.kind, hir::ExprKind::Unary(hir::UnOp::Deref, _)) {
303                 // Clear previous flag; after a pointer indirection it does not apply any more.
304                 inside_union = false;
305             }
306             if source.is_union() {
307                 inside_union = true;
308             }
309             // Fix up the autoderefs. Autorefs can only occur immediately preceding
310             // overloaded place ops, and will be fixed by them in order to get
311             // the correct region.
312             // Do not mutate adjustments in place, but rather take them,
313             // and replace them after mutating them, to avoid having the
314             // typeck results borrowed during (`deref_mut`) method resolution.
315             let previous_adjustments =
316                 self.typeck_results.borrow_mut().adjustments_mut().remove(expr.hir_id);
317             if let Some(mut adjustments) = previous_adjustments {
318                 for adjustment in &mut adjustments {
319                     if let Adjust::Deref(Some(ref mut deref)) = adjustment.kind
320                         && let Some(ok) = self.try_mutable_overloaded_place_op(
321                             expr.span,
322                             source,
323                             &[],
324                             PlaceOp::Deref,
325                         )
326                     {
327                         let method = self.register_infer_ok_obligations(ok);
328                         if let ty::Ref(region, _, mutbl) = *method.sig.output().kind() {
329                             *deref = OverloadedDeref { region, mutbl, span: deref.span };
330                         }
331                         // If this is a union field, also throw an error for `DerefMut` of `ManuallyDrop` (see RFC 2514).
332                         // This helps avoid accidental drops.
333                         if inside_union
334                             && source.ty_adt_def().map_or(false, |adt| adt.is_manually_drop())
335                         {
336                             let mut err = self.tcx.sess.struct_span_err(
337                                 expr.span,
338                                 "not automatically applying `DerefMut` on `ManuallyDrop` union field",
339                             );
340                             err.help(
341                                 "writing to this reference calls the destructor for the old value",
342                             );
343                             err.help("add an explicit `*` if that is desired, or call `ptr::write` to not run the destructor");
344                             err.emit();
345                         }
346                     }
347                     source = adjustment.target;
348                 }
349                 self.typeck_results.borrow_mut().adjustments_mut().insert(expr.hir_id, adjustments);
350             }
351
352             match expr.kind {
353                 hir::ExprKind::Index(base_expr, ..) => {
354                     self.convert_place_op_to_mutable(PlaceOp::Index, expr, base_expr);
355                 }
356                 hir::ExprKind::Unary(hir::UnOp::Deref, base_expr) => {
357                     self.convert_place_op_to_mutable(PlaceOp::Deref, expr, base_expr);
358                 }
359                 _ => {}
360             }
361         }
362     }
363
364     fn convert_place_op_to_mutable(
365         &self,
366         op: PlaceOp,
367         expr: &hir::Expr<'_>,
368         base_expr: &hir::Expr<'_>,
369     ) {
370         debug!("convert_place_op_to_mutable({:?}, {:?}, {:?})", op, expr, base_expr);
371         if !self.typeck_results.borrow().is_method_call(expr) {
372             debug!("convert_place_op_to_mutable - builtin, nothing to do");
373             return;
374         }
375
376         // Need to deref because overloaded place ops take self by-reference.
377         let base_ty = self
378             .typeck_results
379             .borrow()
380             .expr_ty_adjusted(base_expr)
381             .builtin_deref(false)
382             .expect("place op takes something that is not a ref")
383             .ty;
384
385         let arg_ty = match op {
386             PlaceOp::Deref => None,
387             PlaceOp::Index => {
388                 // We would need to recover the `T` used when we resolve `<_ as Index<T>>::index`
389                 // in try_index_step. This is the subst at index 1.
390                 //
391                 // Note: we should *not* use `expr_ty` of index_expr here because autoderef
392                 // during coercions can cause type of index_expr to differ from `T` (#72002).
393                 // We also could not use `expr_ty_adjusted` of index_expr because reborrowing
394                 // during coercions can also cause type of index_expr to differ from `T`,
395                 // which can potentially cause regionck failure (#74933).
396                 Some(self.typeck_results.borrow().node_substs(expr.hir_id).type_at(1))
397             }
398         };
399         let arg_tys = match arg_ty {
400             None => &[],
401             Some(ref ty) => slice::from_ref(ty),
402         };
403
404         let method = self.try_mutable_overloaded_place_op(expr.span, base_ty, arg_tys, op);
405         let method = match method {
406             Some(ok) => self.register_infer_ok_obligations(ok),
407             // Couldn't find the mutable variant of the place op, keep the
408             // current, immutable version.
409             None => return,
410         };
411         debug!("convert_place_op_to_mutable: method={:?}", method);
412         self.write_method_call(expr.hir_id, method);
413
414         let ty::Ref(region, _, hir::Mutability::Mut) = method.sig.inputs()[0].kind() else {
415             span_bug!(expr.span, "input to mutable place op is not a mut ref?");
416         };
417
418         // Convert the autoref in the base expr to mutable with the correct
419         // region and mutability.
420         let base_expr_ty = self.node_ty(base_expr.hir_id);
421         if let Some(adjustments) =
422             self.typeck_results.borrow_mut().adjustments_mut().get_mut(base_expr.hir_id)
423         {
424             let mut source = base_expr_ty;
425             for adjustment in &mut adjustments[..] {
426                 if let Adjust::Borrow(AutoBorrow::Ref(..)) = adjustment.kind {
427                     debug!("convert_place_op_to_mutable: converting autoref {:?}", adjustment);
428                     let mutbl = AutoBorrowMutability::Mut {
429                         // Deref/indexing can be desugared to a method call,
430                         // so maybe we could use two-phase here.
431                         // See the documentation of AllowTwoPhase for why that's
432                         // not the case today.
433                         allow_two_phase_borrow: AllowTwoPhase::No,
434                     };
435                     adjustment.kind = Adjust::Borrow(AutoBorrow::Ref(*region, mutbl));
436                     adjustment.target = self
437                         .tcx
438                         .mk_ref(*region, ty::TypeAndMut { ty: source, mutbl: mutbl.into() });
439                 }
440                 source = adjustment.target;
441             }
442
443             // If we have an autoref followed by unsizing at the end, fix the unsize target.
444             if let [
445                 ..,
446                 Adjustment { kind: Adjust::Borrow(AutoBorrow::Ref(..)), .. },
447                 Adjustment { kind: Adjust::Pointer(PointerCast::Unsize), ref mut target },
448             ] = adjustments[..]
449             {
450                 *target = method.sig.inputs()[0];
451             }
452         }
453     }
454 }