]>
Commit | Line | Data |
---|---|---|
487cf647 | 1 | use rustc_errors::{Applicability, Diagnostic}; |
dfeec247 | 2 | use rustc_hir as hir; |
f2b60f7d | 3 | use rustc_hir::intravisit::Visitor; |
dfeec247 | 4 | use rustc_hir::Node; |
6a06907d | 5 | use rustc_middle::hir::map::Map; |
ba9703b0 XL |
6 | use rustc_middle::mir::{Mutability, Place, PlaceRef, ProjectionElem}; |
7 | use rustc_middle::ty::{self, Ty, TyCtxt}; | |
5869c6ff XL |
8 | use rustc_middle::{ |
9 | hir::place::PlaceBase, | |
f2b60f7d | 10 | mir::{self, BindingForm, ClearCrossCrate, Local, LocalDecl, LocalInfo, LocalKind, Location}, |
5869c6ff | 11 | }; |
ba9703b0 | 12 | use rustc_span::source_map::DesugaringKind; |
29967ef6 | 13 | use rustc_span::symbol::{kw, Symbol}; |
f2b60f7d | 14 | use rustc_span::{sym, BytePos, Span}; |
8faf50e0 | 15 | |
c295e0f8 XL |
16 | use crate::diagnostics::BorrowedContentSource; |
17 | use crate::MirBorrowckCtxt; | |
18 | use rustc_const_eval::util::collect_writes::FindAssignments; | |
8faf50e0 | 19 | |
b7449926 | 20 | #[derive(Copy, Clone, Debug, Eq, PartialEq)] |
60c5eb7d | 21 | pub(crate) enum AccessKind { |
8faf50e0 XL |
22 | MutableBorrow, |
23 | Mutate, | |
8faf50e0 XL |
24 | } |
25 | ||
dc9dc135 | 26 | impl<'a, 'tcx> MirBorrowckCtxt<'a, 'tcx> { |
60c5eb7d | 27 | pub(crate) fn report_mutability_error( |
8faf50e0 | 28 | &mut self, |
ba9703b0 | 29 | access_place: Place<'tcx>, |
8faf50e0 | 30 | span: Span, |
74b04a01 | 31 | the_place_err: PlaceRef<'tcx>, |
8faf50e0 XL |
32 | error_access: AccessKind, |
33 | location: Location, | |
34 | ) { | |
b7449926 XL |
35 | debug!( |
36 | "report_mutability_error(\ | |
37 | access_place={:?}, span={:?}, the_place_err={:?}, error_access={:?}, location={:?},\ | |
38 | )", | |
39 | access_place, span, the_place_err, error_access, location, | |
40 | ); | |
41 | ||
8faf50e0 XL |
42 | let mut err; |
43 | let item_msg; | |
44 | let reason; | |
416331ca | 45 | let mut opt_source = None; |
3c0e092e | 46 | let access_place_desc = self.describe_any_place(access_place.as_ref()); |
b7449926 | 47 | debug!("report_mutability_error: access_place_desc={:?}", access_place_desc); |
8faf50e0 XL |
48 | |
49 | match the_place_err { | |
dfeec247 | 50 | PlaceRef { local, projection: [] } => { |
3c0e092e | 51 | item_msg = access_place_desc; |
e74abb32 | 52 | if access_place.as_local().is_some() { |
8faf50e0 XL |
53 | reason = ", as it is not declared as mutable".to_string(); |
54 | } else { | |
74b04a01 | 55 | let name = self.local_names[local].expect("immutable unnamed local"); |
04454e1e | 56 | reason = format!(", as `{name}` is not declared as mutable"); |
8faf50e0 XL |
57 | } |
58 | } | |
59 | ||
416331ca | 60 | PlaceRef { |
dfeec247 | 61 | local, |
e1599b0c | 62 | projection: [proj_base @ .., ProjectionElem::Field(upvar_index, _)], |
416331ca | 63 | } => { |
8faf50e0 | 64 | debug_assert!(is_closure_or_generator( |
f9f354fc | 65 | Place::ty_from(local, proj_base, self.body, self.infcx.tcx).ty |
dfeec247 | 66 | )); |
8faf50e0 | 67 | |
5869c6ff XL |
68 | let imm_borrow_derefed = self.upvars[upvar_index.index()] |
69 | .place | |
70 | .place | |
71 | .deref_tys() | |
72 | .any(|ty| matches!(ty.kind(), ty::Ref(.., hir::Mutability::Not))); | |
73 | ||
74 | // If the place is immutable then: | |
75 | // | |
94222f64 | 76 | // - Either we deref an immutable ref to get to our final place. |
5869c6ff XL |
77 | // - We don't capture derefs of raw ptrs |
78 | // - Or the final place is immut because the root variable of the capture | |
79 | // isn't marked mut and we should suggest that to the user. | |
80 | if imm_borrow_derefed { | |
81 | // If we deref an immutable ref then the suggestion here doesn't help. | |
82 | return; | |
8faf50e0 | 83 | } else { |
3c0e092e | 84 | item_msg = access_place_desc; |
5869c6ff XL |
85 | if self.is_upvar_field_projection(access_place.as_ref()).is_some() { |
86 | reason = ", as it is not declared as mutable".to_string(); | |
87 | } else { | |
17df50a5 | 88 | let name = self.upvars[upvar_index.index()].place.to_string(self.infcx.tcx); |
04454e1e | 89 | reason = format!(", as `{name}` is not declared as mutable"); |
5869c6ff | 90 | } |
8faf50e0 XL |
91 | } |
92 | } | |
93 | ||
dfeec247 | 94 | PlaceRef { local, projection: [ProjectionElem::Deref] } |
74b04a01 | 95 | if self.body.local_decls[local].is_ref_for_guard() => |
dfeec247 | 96 | { |
3c0e092e | 97 | item_msg = access_place_desc; |
60c5eb7d XL |
98 | reason = ", as it is immutable for the pattern guard".to_string(); |
99 | } | |
dfeec247 | 100 | PlaceRef { local, projection: [ProjectionElem::Deref] } |
74b04a01 | 101 | if self.body.local_decls[local].is_ref_to_static() => |
dfeec247 | 102 | { |
60c5eb7d | 103 | if access_place.projection.len() == 1 { |
04454e1e | 104 | item_msg = format!("immutable static item {access_place_desc}"); |
60c5eb7d XL |
105 | reason = String::new(); |
106 | } else { | |
3c0e092e | 107 | item_msg = access_place_desc; |
74b04a01 | 108 | let local_info = &self.body.local_decls[local].local_info; |
f9f354fc | 109 | if let Some(box LocalInfo::StaticRef { def_id, .. }) = *local_info { |
60c5eb7d | 110 | let static_name = &self.infcx.tcx.item_name(def_id); |
04454e1e | 111 | reason = format!(", as `{static_name}` is an immutable static item"); |
60c5eb7d XL |
112 | } else { |
113 | bug!("is_ref_to_static return true, but not ref to static?"); | |
114 | } | |
115 | } | |
116 | } | |
dfeec247 | 117 | PlaceRef { local: _, projection: [proj_base @ .., ProjectionElem::Deref] } => { |
17df50a5 | 118 | if the_place_err.local == ty::CAPTURE_STRUCT_LOCAL |
dfeec247 XL |
119 | && proj_base.is_empty() |
120 | && !self.upvars.is_empty() | |
121 | { | |
3c0e092e | 122 | item_msg = access_place_desc; |
17df50a5 XL |
123 | debug_assert!( |
124 | self.body.local_decls[ty::CAPTURE_STRUCT_LOCAL].ty.is_region_ptr() | |
125 | ); | |
8faf50e0 | 126 | debug_assert!(is_closure_or_generator( |
416331ca | 127 | Place::ty_from( |
dfeec247 | 128 | the_place_err.local, |
416331ca | 129 | the_place_err.projection, |
f9f354fc | 130 | self.body, |
416331ca XL |
131 | self.infcx.tcx |
132 | ) | |
133 | .ty | |
8faf50e0 XL |
134 | )); |
135 | ||
dfeec247 XL |
136 | reason = if self.is_upvar_field_projection(access_place.as_ref()).is_some() { |
137 | ", as it is a captured variable in a `Fn` closure".to_string() | |
138 | } else { | |
139 | ", as `Fn` closures cannot mutate their captured variables".to_string() | |
140 | } | |
8faf50e0 | 141 | } else { |
416331ca | 142 | let source = self.borrowed_content_source(PlaceRef { |
dfeec247 | 143 | local: the_place_err.local, |
e1599b0c | 144 | projection: proj_base, |
416331ca | 145 | }); |
ba9703b0 | 146 | let pointer_type = source.describe_for_immutable_place(self.infcx.tcx); |
416331ca | 147 | opt_source = Some(source); |
3c0e092e | 148 | if let Some(desc) = self.describe_place(access_place.as_ref()) { |
04454e1e | 149 | item_msg = format!("`{desc}`"); |
8faf50e0 | 150 | reason = match error_access { |
04454e1e | 151 | AccessKind::Mutate => format!(", which is behind {pointer_type}"), |
8faf50e0 | 152 | AccessKind::MutableBorrow => { |
04454e1e | 153 | format!(", as it is behind {pointer_type}") |
8faf50e0 XL |
154 | } |
155 | } | |
156 | } else { | |
04454e1e | 157 | item_msg = format!("data in {pointer_type}"); |
b7449926 | 158 | reason = String::new(); |
8faf50e0 XL |
159 | } |
160 | } | |
161 | } | |
162 | ||
ba9703b0 XL |
163 | PlaceRef { |
164 | local: _, | |
165 | projection: | |
a2a8927a XL |
166 | [ |
167 | .., | |
168 | ProjectionElem::Index(_) | |
169 | | ProjectionElem::ConstantIndex { .. } | |
2b03887a | 170 | | ProjectionElem::OpaqueCast { .. } |
a2a8927a XL |
171 | | ProjectionElem::Subslice { .. } |
172 | | ProjectionElem::Downcast(..), | |
173 | ], | |
ba9703b0 | 174 | } => bug!("Unexpected immutable place."), |
8faf50e0 XL |
175 | } |
176 | ||
b7449926 XL |
177 | debug!("report_mutability_error: item_msg={:?}, reason={:?}", item_msg, reason); |
178 | ||
8faf50e0 XL |
179 | // `act` and `acted_on` are strings that let us abstract over |
180 | // the verbs used in some diagnostic messages. | |
181 | let act; | |
182 | let acted_on; | |
9c376795 FG |
183 | let mut suggest = true; |
184 | let mut mut_error = None; | |
185 | let mut count = 1; | |
8faf50e0 | 186 | |
8faf50e0 | 187 | let span = match error_access { |
8faf50e0 | 188 | AccessKind::Mutate => { |
416331ca | 189 | err = self.cannot_assign(span, &(item_msg + &reason)); |
8faf50e0 XL |
190 | act = "assign"; |
191 | acted_on = "written"; | |
192 | span | |
193 | } | |
194 | AccessKind::MutableBorrow => { | |
195 | act = "borrow as mutable"; | |
196 | acted_on = "borrowed as mutable"; | |
197 | ||
b7449926 XL |
198 | let borrow_spans = self.borrow_spans(span, location); |
199 | let borrow_span = borrow_spans.args_or_use(); | |
9c376795 FG |
200 | match the_place_err { |
201 | PlaceRef { local, projection: [] } | |
202 | if self.body.local_decls[local].can_be_made_mutable() => | |
203 | { | |
204 | let span = self.body.local_decls[local].source_info.span; | |
205 | mut_error = Some(span); | |
206 | if let Some((buffer, c)) = self.get_buffered_mut_error(span) { | |
207 | // We've encountered a second (or more) attempt to mutably borrow an | |
208 | // immutable binding, so the likely problem is with the binding | |
209 | // declaration, not the use. We collect these in a single diagnostic | |
210 | // and make the binding the primary span of the error. | |
211 | err = buffer; | |
212 | count = c + 1; | |
213 | if count == 2 { | |
214 | err.replace_span_with(span, false); | |
215 | err.span_label(span, "not mutable"); | |
216 | } | |
217 | suggest = false; | |
218 | } else { | |
219 | err = self.cannot_borrow_path_as_mutable_because( | |
220 | borrow_span, | |
221 | &item_msg, | |
222 | &reason, | |
223 | ); | |
224 | } | |
225 | } | |
226 | _ => { | |
227 | err = self.cannot_borrow_path_as_mutable_because( | |
228 | borrow_span, | |
229 | &item_msg, | |
230 | &reason, | |
231 | ); | |
232 | } | |
233 | } | |
234 | if suggest { | |
235 | borrow_spans.var_span_label( | |
236 | &mut err, | |
237 | format!( | |
238 | "mutable borrow occurs due to use of {} in closure", | |
239 | self.describe_any_place(access_place.as_ref()), | |
240 | ), | |
241 | "mutable", | |
242 | ); | |
243 | } | |
b7449926 | 244 | borrow_span |
8faf50e0 XL |
245 | } |
246 | }; | |
247 | ||
b7449926 XL |
248 | debug!("report_mutability_error: act={:?}, acted_on={:?}", act, acted_on); |
249 | ||
8faf50e0 | 250 | match the_place_err { |
0bf4aa26 XL |
251 | // Suggest making an existing shared borrow in a struct definition a mutable borrow. |
252 | // | |
253 | // This is applicable when we have a deref of a field access to a deref of a local - | |
254 | // something like `*((*_1).0`. The local that we get will be a reference to the | |
255 | // struct we've got a field access of (it must be a reference since there's a deref | |
256 | // after the field access). | |
416331ca | 257 | PlaceRef { |
dfeec247 XL |
258 | local, |
259 | projection: | |
487cf647 FG |
260 | [ |
261 | proj_base @ .., | |
a2a8927a XL |
262 | ProjectionElem::Deref, |
263 | ProjectionElem::Field(field, _), | |
264 | ProjectionElem::Deref, | |
265 | ], | |
416331ca | 266 | } => { |
9c376795 | 267 | err.span_label(span, format!("cannot {act}")); |
0bf4aa26 | 268 | |
a2a8927a | 269 | if let Some(span) = get_mut_span_in_struct_field( |
0bf4aa26 | 270 | self.infcx.tcx, |
f9f354fc | 271 | Place::ty_from(local, proj_base, self.body, self.infcx.tcx).ty, |
487cf647 | 272 | *field, |
0bf4aa26 | 273 | ) { |
a2a8927a | 274 | err.span_suggestion_verbose( |
0bf4aa26 XL |
275 | span, |
276 | "consider changing this to be mutable", | |
04454e1e | 277 | " mut ", |
0bf4aa26 XL |
278 | Applicability::MaybeIncorrect, |
279 | ); | |
280 | } | |
dfeec247 | 281 | } |
0bf4aa26 XL |
282 | |
283 | // Suggest removing a `&mut` from the use of a mutable reference. | |
dfeec247 | 284 | PlaceRef { local, projection: [] } |
29967ef6 XL |
285 | if self |
286 | .body | |
287 | .local_decls | |
288 | .get(local) | |
289 | .map(|l| mut_borrow_of_mutable_ref(l, self.local_names[local])) | |
290 | .unwrap_or(false) => | |
dfeec247 | 291 | { |
94222f64 | 292 | let decl = &self.body.local_decls[local]; |
9c376795 | 293 | err.span_label(span, format!("cannot {act}")); |
94222f64 XL |
294 | if let Some(mir::Statement { |
295 | source_info, | |
296 | kind: | |
297 | mir::StatementKind::Assign(box ( | |
298 | _, | |
299 | mir::Rvalue::Ref( | |
300 | _, | |
301 | mir::BorrowKind::Mut { allow_two_phase_borrow: false }, | |
302 | _, | |
303 | ), | |
304 | )), | |
305 | .. | |
306 | }) = &self.body[location.block].statements.get(location.statement_index) | |
307 | { | |
308 | match decl.local_info { | |
309 | Some(box LocalInfo::User(ClearCrossCrate::Set(BindingForm::Var( | |
310 | mir::VarBindingForm { | |
311 | binding_mode: ty::BindingMode::BindByValue(Mutability::Not), | |
312 | opt_ty_info: Some(sp), | |
313 | opt_match_place: _, | |
314 | pat_span: _, | |
315 | }, | |
316 | )))) => { | |
9c376795 FG |
317 | if suggest { |
318 | err.span_note(sp, "the binding is already a mutable borrow"); | |
319 | } | |
94222f64 XL |
320 | } |
321 | _ => { | |
322 | err.span_note( | |
323 | decl.source_info.span, | |
324 | "the binding is already a mutable borrow", | |
325 | ); | |
326 | } | |
327 | } | |
328 | if let Ok(snippet) = | |
329 | self.infcx.tcx.sess.source_map().span_to_snippet(source_info.span) | |
330 | { | |
331 | if snippet.starts_with("&mut ") { | |
332 | // We don't have access to the HIR to get accurate spans, but we can | |
333 | // give a best effort structured suggestion. | |
334 | err.span_suggestion_verbose( | |
335 | source_info.span.with_hi(source_info.span.lo() + BytePos(5)), | |
336 | "try removing `&mut` here", | |
923072b8 | 337 | "", |
94222f64 XL |
338 | Applicability::MachineApplicable, |
339 | ); | |
340 | } else { | |
341 | // This can occur with things like `(&mut self).foo()`. | |
342 | err.span_help(source_info.span, "try removing `&mut` here"); | |
343 | } | |
344 | } else { | |
345 | err.span_help(source_info.span, "try removing `&mut` here"); | |
346 | } | |
9ffffee4 | 347 | } else if decl.mutability.is_not() { |
9c376795 | 348 | if matches!( |
94222f64 XL |
349 | decl.local_info, |
350 | Some(box LocalInfo::User(ClearCrossCrate::Set(BindingForm::ImplicitSelf( | |
f2b60f7d | 351 | hir::ImplicitSelfKind::MutRef |
9c376795 FG |
352 | ),))) |
353 | ) { | |
354 | err.note( | |
355 | "as `Self` may be unsized, this call attempts to take `&mut &mut self`", | |
356 | ); | |
357 | err.note("however, `&mut self` expands to `self: &mut Self`, therefore `self` cannot be borrowed mutably"); | |
358 | } else { | |
359 | err.span_suggestion_verbose( | |
360 | decl.source_info.span.shrink_to_lo(), | |
361 | "consider making the binding mutable", | |
362 | "mut ", | |
363 | Applicability::MachineApplicable, | |
364 | ); | |
365 | }; | |
94222f64 | 366 | } |
dfeec247 | 367 | } |
0bf4aa26 | 368 | |
8faf50e0 XL |
369 | // We want to suggest users use `let mut` for local (user |
370 | // variable) mutations... | |
dfeec247 | 371 | PlaceRef { local, projection: [] } |
74b04a01 | 372 | if self.body.local_decls[local].can_be_made_mutable() => |
dfeec247 | 373 | { |
8faf50e0 XL |
374 | // ... but it doesn't make sense to suggest it on |
375 | // variables that are `ref x`, `ref mut x`, `&self`, | |
376 | // or `&mut self` (such variables are simply not | |
377 | // mutable). | |
74b04a01 | 378 | let local_decl = &self.body.local_decls[local]; |
8faf50e0 XL |
379 | assert_eq!(local_decl.mutability, Mutability::Not); |
380 | ||
9c376795 FG |
381 | if count < 10 { |
382 | err.span_label(span, format!("cannot {act}")); | |
383 | } | |
384 | if suggest { | |
385 | err.span_suggestion_verbose( | |
386 | local_decl.source_info.span.shrink_to_lo(), | |
387 | "consider changing this to be mutable", | |
9ffffee4 | 388 | "mut ", |
9c376795 FG |
389 | Applicability::MachineApplicable, |
390 | ); | |
391 | let tcx = self.infcx.tcx; | |
392 | if let ty::Closure(id, _) = *the_place_err.ty(self.body, tcx).ty.kind() { | |
393 | self.show_mutating_upvar(tcx, id.expect_local(), the_place_err, &mut err); | |
394 | } | |
5869c6ff | 395 | } |
8faf50e0 XL |
396 | } |
397 | ||
398 | // Also suggest adding mut for upvars | |
416331ca | 399 | PlaceRef { |
dfeec247 | 400 | local, |
e1599b0c | 401 | projection: [proj_base @ .., ProjectionElem::Field(upvar_index, _)], |
416331ca | 402 | } => { |
8faf50e0 | 403 | debug_assert!(is_closure_or_generator( |
f9f354fc | 404 | Place::ty_from(local, proj_base, self.body, self.infcx.tcx).ty |
8faf50e0 XL |
405 | )); |
406 | ||
5869c6ff XL |
407 | let captured_place = &self.upvars[upvar_index.index()].place; |
408 | ||
9c376795 | 409 | err.span_label(span, format!("cannot {act}")); |
8faf50e0 | 410 | |
5869c6ff XL |
411 | let upvar_hir_id = captured_place.get_root_variable(); |
412 | ||
064997fb | 413 | if let Some(Node::Pat(pat)) = self.infcx.tcx.hir().find(upvar_hir_id) |
04454e1e | 414 | && let hir::PatKind::Binding( |
f2b60f7d | 415 | hir::BindingAnnotation::NONE, |
8faf50e0 XL |
416 | _, |
417 | upvar_ident, | |
418 | _, | |
e74abb32 | 419 | ) = pat.kind |
04454e1e FG |
420 | { |
421 | err.span_suggestion( | |
422 | upvar_ident.span, | |
423 | "consider changing this to be mutable", | |
424 | format!("mut {}", upvar_ident.name), | |
425 | Applicability::MachineApplicable, | |
426 | ); | |
8faf50e0 | 427 | } |
5869c6ff XL |
428 | |
429 | let tcx = self.infcx.tcx; | |
430 | if let ty::Ref(_, ty, Mutability::Mut) = the_place_err.ty(self.body, tcx).ty.kind() | |
04454e1e | 431 | && let ty::Closure(id, _) = *ty.kind() |
5869c6ff | 432 | { |
064997fb | 433 | self.show_mutating_upvar(tcx, id.expect_local(), the_place_err, &mut err); |
5869c6ff | 434 | } |
8faf50e0 XL |
435 | } |
436 | ||
437 | // complete hack to approximate old AST-borrowck | |
438 | // diagnostic: if the span starts with a mutable borrow of | |
439 | // a local variable, then just suggest the user remove it. | |
dfeec247 | 440 | PlaceRef { local: _, projection: [] } |
487cf647 FG |
441 | if self |
442 | .infcx | |
443 | .tcx | |
444 | .sess | |
445 | .source_map() | |
446 | .span_to_snippet(span) | |
447 | .map_or(false, |snippet| snippet.starts_with("&mut ")) => | |
8faf50e0 | 448 | { |
9c376795 | 449 | err.span_label(span, format!("cannot {act}")); |
94222f64 XL |
450 | err.span_suggestion( |
451 | span, | |
452 | "try removing `&mut` here", | |
923072b8 | 453 | "", |
94222f64 XL |
454 | Applicability::MaybeIncorrect, |
455 | ); | |
8faf50e0 XL |
456 | } |
457 | ||
dfeec247 | 458 | PlaceRef { local, projection: [ProjectionElem::Deref] } |
74b04a01 | 459 | if self.body.local_decls[local].is_ref_for_guard() => |
dfeec247 | 460 | { |
9c376795 | 461 | err.span_label(span, format!("cannot {act}")); |
8faf50e0 XL |
462 | err.note( |
463 | "variables bound in patterns are immutable until the end of the pattern guard", | |
464 | ); | |
465 | } | |
466 | ||
467 | // We want to point out when a `&` can be readily replaced | |
468 | // with an `&mut`. | |
469 | // | |
470 | // FIXME: can this case be generalized to work for an | |
471 | // arbitrary base for the projection? | |
dfeec247 | 472 | PlaceRef { local, projection: [ProjectionElem::Deref] } |
74b04a01 | 473 | if self.body.local_decls[local].is_user_variable() => |
8faf50e0 | 474 | { |
74b04a01 | 475 | let local_decl = &self.body.local_decls[local]; |
8faf50e0 XL |
476 | |
477 | let (pointer_sigil, pointer_desc) = if local_decl.ty.is_region_ptr() { | |
478 | ("&", "reference") | |
479 | } else { | |
480 | ("*const", "pointer") | |
481 | }; | |
482 | ||
74b04a01 | 483 | match self.local_names[local] { |
dc9dc135 | 484 | Some(name) if !local_decl.from_compiler_desugaring() => { |
064997fb FG |
485 | let label = match local_decl.local_info.as_deref().unwrap() { |
486 | LocalInfo::User(ClearCrossCrate::Set( | |
74b04a01 | 487 | mir::BindingForm::ImplicitSelf(_), |
ba9703b0 XL |
488 | )) => { |
489 | let (span, suggestion) = | |
490 | suggest_ampmut_self(self.infcx.tcx, local_decl); | |
491 | Some((true, span, suggestion)) | |
492 | } | |
74b04a01 | 493 | |
064997fb | 494 | LocalInfo::User(ClearCrossCrate::Set(mir::BindingForm::Var( |
74b04a01 XL |
495 | mir::VarBindingForm { |
496 | binding_mode: ty::BindingMode::BindByValue(_), | |
497 | opt_ty_info, | |
498 | .. | |
499 | }, | |
ba9703b0 XL |
500 | ))) => { |
501 | // check if the RHS is from desugaring | |
3c0e092e XL |
502 | let opt_assignment_rhs_span = |
503 | self.body.find_assignments(local).first().map(|&location| { | |
504 | if let Some(mir::Statement { | |
505 | source_info: _, | |
506 | kind: | |
507 | mir::StatementKind::Assign(box ( | |
508 | _, | |
509 | mir::Rvalue::Use(mir::Operand::Copy(place)), | |
510 | )), | |
511 | }) = self.body[location.block] | |
512 | .statements | |
513 | .get(location.statement_index) | |
514 | { | |
515 | self.body.local_decls[place.local].source_info.span | |
516 | } else { | |
517 | self.body.source_info(location).span | |
518 | } | |
519 | }); | |
520 | match opt_assignment_rhs_span.and_then(|s| s.desugaring_kind()) { | |
ba9703b0 | 521 | // on for loops, RHS points to the iterator part |
3c0e092e | 522 | Some(DesugaringKind::ForLoop) => { |
6a06907d | 523 | self.suggest_similar_mut_method_for_for_loop(&mut err); |
064997fb FG |
524 | err.span_label(opt_assignment_rhs_span.unwrap(), format!( |
525 | "this iterator yields `{pointer_sigil}` {pointer_desc}s", | |
526 | )); | |
527 | None | |
6a06907d | 528 | } |
ba9703b0 XL |
529 | // don't create labels for compiler-generated spans |
530 | Some(_) => None, | |
531 | None => { | |
064997fb | 532 | let label = if name != kw::SelfLower { |
5099ac24 FG |
533 | suggest_ampmut( |
534 | self.infcx.tcx, | |
535 | local_decl, | |
536 | opt_assignment_rhs_span, | |
537 | *opt_ty_info, | |
538 | ) | |
539 | } else { | |
540 | match local_decl.local_info.as_deref() { | |
541 | Some(LocalInfo::User(ClearCrossCrate::Set( | |
542 | mir::BindingForm::Var(mir::VarBindingForm { | |
543 | opt_ty_info: None, | |
544 | .. | |
545 | }), | |
546 | ))) => { | |
064997fb FG |
547 | let (span, sugg) = suggest_ampmut_self( |
548 | self.infcx.tcx, | |
549 | local_decl, | |
550 | ); | |
551 | (true, span, sugg) | |
5099ac24 FG |
552 | } |
553 | // explicit self (eg `self: &'a Self`) | |
554 | _ => suggest_ampmut( | |
555 | self.infcx.tcx, | |
556 | local_decl, | |
557 | opt_assignment_rhs_span, | |
558 | *opt_ty_info, | |
559 | ), | |
560 | } | |
561 | }; | |
064997fb | 562 | Some(label) |
ba9703b0 XL |
563 | } |
564 | } | |
565 | } | |
74b04a01 | 566 | |
064997fb | 567 | LocalInfo::User(ClearCrossCrate::Set(mir::BindingForm::Var( |
74b04a01 XL |
568 | mir::VarBindingForm { |
569 | binding_mode: ty::BindingMode::BindByReference(_), | |
570 | .. | |
571 | }, | |
572 | ))) => { | |
573 | let pattern_span = local_decl.source_info.span; | |
574 | suggest_ref_mut(self.infcx.tcx, pattern_span) | |
ba9703b0 | 575 | .map(|replacement| (true, pattern_span, replacement)) |
74b04a01 XL |
576 | } |
577 | ||
064997fb | 578 | LocalInfo::User(ClearCrossCrate::Clear) => { |
74b04a01 XL |
579 | bug!("saw cleared local state") |
580 | } | |
581 | ||
582 | _ => unreachable!(), | |
583 | }; | |
584 | ||
ba9703b0 XL |
585 | match label { |
586 | Some((true, err_help_span, suggested_code)) => { | |
17df50a5 XL |
587 | let (is_trait_sig, local_trait) = self.is_error_in_trait(local); |
588 | if !is_trait_sig { | |
9c376795 | 589 | err.span_suggestion_verbose( |
17df50a5 XL |
590 | err_help_span, |
591 | &format!( | |
04454e1e | 592 | "consider changing this to be a mutable {pointer_desc}" |
17df50a5 XL |
593 | ), |
594 | suggested_code, | |
595 | Applicability::MachineApplicable, | |
596 | ); | |
597 | } else if let Some(x) = local_trait { | |
9c376795 | 598 | err.span_suggestion_verbose( |
17df50a5 XL |
599 | x, |
600 | &format!( | |
04454e1e | 601 | "consider changing that to be a mutable {pointer_desc}" |
17df50a5 XL |
602 | ), |
603 | suggested_code, | |
604 | Applicability::MachineApplicable, | |
605 | ); | |
606 | } | |
ba9703b0 XL |
607 | } |
608 | Some((false, err_label_span, message)) => { | |
9ffffee4 FG |
609 | struct BindingFinder { |
610 | span: Span, | |
611 | hir_id: Option<hir::HirId>, | |
612 | } | |
613 | ||
614 | impl<'tcx> Visitor<'tcx> for BindingFinder { | |
615 | fn visit_stmt(&mut self, s: &'tcx hir::Stmt<'tcx>) { | |
616 | if let hir::StmtKind::Local(local) = s.kind { | |
617 | if local.pat.span == self.span { | |
618 | self.hir_id = Some(local.hir_id); | |
619 | } | |
620 | } | |
621 | hir::intravisit::walk_stmt(self, s); | |
622 | } | |
623 | } | |
624 | let hir_map = self.infcx.tcx.hir(); | |
625 | let def_id = self.body.source.def_id(); | |
626 | let hir_id = hir_map.local_def_id_to_hir_id(def_id.expect_local()); | |
627 | let node = hir_map.find(hir_id); | |
628 | let hir_id = if let Some(hir::Node::Item(item)) = node | |
629 | && let hir::ItemKind::Fn(.., body_id) = item.kind | |
630 | { | |
631 | let body = hir_map.body(body_id); | |
632 | let mut v = BindingFinder { | |
633 | span: err_label_span, | |
634 | hir_id: None, | |
635 | }; | |
636 | v.visit_body(body); | |
637 | v.hir_id | |
638 | } else { | |
639 | None | |
640 | }; | |
641 | if let Some(hir_id) = hir_id | |
642 | && let Some(hir::Node::Local(local)) = hir_map.find(hir_id) | |
643 | { | |
644 | let (changing, span, sugg) = match local.ty { | |
645 | Some(ty) => ("changing", ty.span, message), | |
646 | None => ( | |
647 | "specifying", | |
648 | local.pat.span.shrink_to_hi(), | |
649 | format!(": {message}"), | |
650 | ), | |
651 | }; | |
652 | err.span_suggestion_verbose( | |
653 | span, | |
654 | &format!("consider {changing} this binding's type"), | |
655 | sugg, | |
656 | Applicability::HasPlaceholders, | |
657 | ); | |
658 | } else { | |
659 | err.span_label( | |
660 | err_label_span, | |
661 | &format!( | |
662 | "consider changing this binding's type to be: `{message}`" | |
663 | ), | |
664 | ); | |
665 | } | |
ba9703b0 XL |
666 | } |
667 | None => {} | |
74b04a01 | 668 | } |
dc9dc135 XL |
669 | err.span_label( |
670 | span, | |
671 | format!( | |
9c376795 FG |
672 | "`{name}` is a `{pointer_sigil}` {pointer_desc}, \ |
673 | so the data it refers to cannot be {acted_on}", | |
dc9dc135 XL |
674 | ), |
675 | ); | |
676 | } | |
677 | _ => { | |
678 | err.span_label( | |
679 | span, | |
9c376795 | 680 | format!("cannot {act} through `{pointer_sigil}` {pointer_desc}"), |
dc9dc135 XL |
681 | ); |
682 | } | |
8faf50e0 XL |
683 | } |
684 | } | |
685 | ||
17df50a5 XL |
686 | PlaceRef { local, projection: [ProjectionElem::Deref] } |
687 | if local == ty::CAPTURE_STRUCT_LOCAL && !self.upvars.is_empty() => | |
688 | { | |
74b04a01 | 689 | self.expected_fn_found_fn_mut_call(&mut err, span, act); |
8faf50e0 XL |
690 | } |
691 | ||
dfeec247 | 692 | PlaceRef { local: _, projection: [.., ProjectionElem::Deref] } => { |
9c376795 | 693 | err.span_label(span, format!("cannot {act}")); |
b7449926 | 694 | |
416331ca XL |
695 | match opt_source { |
696 | Some(BorrowedContentSource::OverloadedDeref(ty)) => { | |
dfeec247 XL |
697 | err.help(&format!( |
698 | "trait `DerefMut` is required to modify through a dereference, \ | |
9c376795 | 699 | but it is not implemented for `{ty}`", |
dfeec247 XL |
700 | )); |
701 | } | |
416331ca | 702 | Some(BorrowedContentSource::OverloadedIndex(ty)) => { |
dfeec247 XL |
703 | err.help(&format!( |
704 | "trait `IndexMut` is required to modify indexed content, \ | |
9c376795 | 705 | but it is not implemented for `{ty}`", |
dfeec247 | 706 | )); |
f2b60f7d | 707 | self.suggest_map_index_mut_alternatives(ty, &mut err, span); |
b7449926 | 708 | } |
416331ca | 709 | _ => (), |
b7449926 XL |
710 | } |
711 | } | |
712 | ||
8faf50e0 | 713 | _ => { |
9c376795 | 714 | err.span_label(span, format!("cannot {act}")); |
8faf50e0 XL |
715 | } |
716 | } | |
717 | ||
9c376795 FG |
718 | if let Some(span) = mut_error { |
719 | self.buffer_mut_error(span, err, count); | |
720 | } else { | |
721 | self.buffer_error(err); | |
722 | } | |
8faf50e0 | 723 | } |
74b04a01 | 724 | |
487cf647 | 725 | fn suggest_map_index_mut_alternatives(&self, ty: Ty<'tcx>, err: &mut Diagnostic, span: Span) { |
f2b60f7d FG |
726 | let Some(adt) = ty.ty_adt_def() else { return }; |
727 | let did = adt.did(); | |
728 | if self.infcx.tcx.is_diagnostic_item(sym::HashMap, did) | |
729 | || self.infcx.tcx.is_diagnostic_item(sym::BTreeMap, did) | |
730 | { | |
487cf647 | 731 | struct V<'a, 'tcx> { |
f2b60f7d | 732 | assign_span: Span, |
487cf647 | 733 | err: &'a mut Diagnostic, |
f2b60f7d FG |
734 | ty: Ty<'tcx>, |
735 | suggested: bool, | |
736 | } | |
487cf647 FG |
737 | impl<'a, 'tcx> Visitor<'tcx> for V<'a, 'tcx> { |
738 | fn visit_stmt(&mut self, stmt: &'tcx hir::Stmt<'tcx>) { | |
f2b60f7d FG |
739 | hir::intravisit::walk_stmt(self, stmt); |
740 | let expr = match stmt.kind { | |
741 | hir::StmtKind::Semi(expr) | hir::StmtKind::Expr(expr) => expr, | |
742 | hir::StmtKind::Local(hir::Local { init: Some(expr), .. }) => expr, | |
743 | _ => { | |
744 | return; | |
745 | } | |
746 | }; | |
747 | if let hir::ExprKind::Assign(place, rv, _sp) = expr.kind | |
748 | && let hir::ExprKind::Index(val, index) = place.kind | |
749 | && (expr.span == self.assign_span || place.span == self.assign_span) | |
750 | { | |
751 | // val[index] = rv; | |
752 | // ---------- place | |
753 | self.err.multipart_suggestions( | |
754 | &format!( | |
755 | "to modify a `{}`, use `.get_mut()`, `.insert()` or the entry API", | |
756 | self.ty, | |
757 | ), | |
758 | vec![ | |
759 | vec![ // val.insert(index, rv); | |
760 | ( | |
761 | val.span.shrink_to_hi().with_hi(index.span.lo()), | |
762 | ".insert(".to_string(), | |
763 | ), | |
764 | ( | |
765 | index.span.shrink_to_hi().with_hi(rv.span.lo()), | |
766 | ", ".to_string(), | |
767 | ), | |
768 | (rv.span.shrink_to_hi(), ")".to_string()), | |
769 | ], | |
770 | vec![ // val.get_mut(index).map(|v| { *v = rv; }); | |
771 | ( | |
772 | val.span.shrink_to_hi().with_hi(index.span.lo()), | |
773 | ".get_mut(".to_string(), | |
774 | ), | |
775 | ( | |
776 | index.span.shrink_to_hi().with_hi(place.span.hi()), | |
777 | ").map(|val| { *val".to_string(), | |
778 | ), | |
779 | ( | |
780 | rv.span.shrink_to_hi(), | |
781 | "; })".to_string(), | |
782 | ), | |
783 | ], | |
784 | vec![ // let x = val.entry(index).or_insert(rv); | |
785 | (val.span.shrink_to_lo(), "let val = ".to_string()), | |
786 | ( | |
787 | val.span.shrink_to_hi().with_hi(index.span.lo()), | |
788 | ".entry(".to_string(), | |
789 | ), | |
790 | ( | |
791 | index.span.shrink_to_hi().with_hi(rv.span.lo()), | |
792 | ").or_insert(".to_string(), | |
793 | ), | |
794 | (rv.span.shrink_to_hi(), ")".to_string()), | |
795 | ], | |
487cf647 | 796 | ], |
f2b60f7d FG |
797 | Applicability::MachineApplicable, |
798 | ); | |
799 | self.suggested = true; | |
800 | } else if let hir::ExprKind::MethodCall(_path, receiver, _, sp) = expr.kind | |
801 | && let hir::ExprKind::Index(val, index) = receiver.kind | |
802 | && expr.span == self.assign_span | |
803 | { | |
804 | // val[index].path(args..); | |
805 | self.err.multipart_suggestion( | |
806 | &format!("to modify a `{}` use `.get_mut()`", self.ty), | |
807 | vec![ | |
808 | ( | |
809 | val.span.shrink_to_hi().with_hi(index.span.lo()), | |
810 | ".get_mut(".to_string(), | |
811 | ), | |
812 | ( | |
813 | index.span.shrink_to_hi().with_hi(receiver.span.hi()), | |
814 | ").map(|val| val".to_string(), | |
815 | ), | |
816 | (sp.shrink_to_hi(), ")".to_string()), | |
817 | ], | |
818 | Applicability::MachineApplicable, | |
819 | ); | |
820 | self.suggested = true; | |
821 | } | |
822 | } | |
823 | } | |
824 | let hir_map = self.infcx.tcx.hir(); | |
825 | let def_id = self.body.source.def_id(); | |
826 | let hir_id = hir_map.local_def_id_to_hir_id(def_id.as_local().unwrap()); | |
827 | let node = hir_map.find(hir_id); | |
828 | let Some(hir::Node::Item(item)) = node else { return; }; | |
829 | let hir::ItemKind::Fn(.., body_id) = item.kind else { return; }; | |
830 | let body = self.infcx.tcx.hir().body(body_id); | |
831 | let mut v = V { assign_span: span, err, ty, suggested: false }; | |
832 | v.visit_body(body); | |
833 | if !v.suggested { | |
834 | err.help(&format!( | |
835 | "to modify a `{ty}`, use `.get_mut()`, `.insert()` or the entry API", | |
836 | )); | |
837 | } | |
838 | } | |
839 | } | |
840 | ||
17df50a5 XL |
841 | /// User cannot make signature of a trait mutable without changing the |
842 | /// trait. So we find if this error belongs to a trait and if so we move | |
843 | /// suggestion to the trait or disable it if it is out of scope of this crate | |
844 | fn is_error_in_trait(&self, local: Local) -> (bool, Option<Span>) { | |
845 | if self.body.local_kind(local) != LocalKind::Arg { | |
846 | return (false, None); | |
847 | } | |
848 | let hir_map = self.infcx.tcx.hir(); | |
849 | let my_def = self.body.source.def_id(); | |
850 | let my_hir = hir_map.local_def_id_to_hir_id(my_def.as_local().unwrap()); | |
5099ac24 | 851 | let Some(td) = |
17df50a5 | 852 | self.infcx.tcx.impl_of_method(my_def).and_then(|x| self.infcx.tcx.trait_id_of_impl(x)) |
5099ac24 | 853 | else { |
17df50a5 XL |
854 | return (false, None); |
855 | }; | |
856 | ( | |
857 | true, | |
5099ac24 FG |
858 | td.as_local().and_then(|tld| match hir_map.find_by_def_id(tld) { |
859 | Some(Node::Item(hir::Item { | |
860 | kind: hir::ItemKind::Trait(_, _, _, _, items), | |
861 | .. | |
862 | })) => { | |
863 | let mut f_in_trait_opt = None; | |
864 | for hir::TraitItemRef { id: fi, kind: k, .. } in *items { | |
865 | let hi = fi.hir_id(); | |
866 | if !matches!(k, hir::AssocItemKind::Fn { .. }) { | |
867 | continue; | |
17df50a5 | 868 | } |
5099ac24 FG |
869 | if hir_map.name(hi) != hir_map.name(my_hir) { |
870 | continue; | |
871 | } | |
872 | f_in_trait_opt = Some(hi); | |
873 | break; | |
17df50a5 | 874 | } |
5099ac24 FG |
875 | f_in_trait_opt.and_then(|f_in_trait| match hir_map.find(f_in_trait) { |
876 | Some(Node::TraitItem(hir::TraitItem { | |
877 | kind: | |
878 | hir::TraitItemKind::Fn( | |
879 | hir::FnSig { decl: hir::FnDecl { inputs, .. }, .. }, | |
880 | _, | |
881 | ), | |
882 | .. | |
883 | })) => { | |
884 | let hir::Ty { span, .. } = inputs[local.index() - 1]; | |
885 | Some(span) | |
886 | } | |
887 | _ => None, | |
888 | }) | |
17df50a5 | 889 | } |
5099ac24 | 890 | _ => None, |
17df50a5 XL |
891 | }), |
892 | ) | |
893 | } | |
894 | ||
5869c6ff XL |
895 | // point to span of upvar making closure call require mutable borrow |
896 | fn show_mutating_upvar( | |
897 | &self, | |
898 | tcx: TyCtxt<'_>, | |
064997fb | 899 | closure_local_def_id: hir::def_id::LocalDefId, |
5869c6ff | 900 | the_place_err: PlaceRef<'tcx>, |
5e7ed085 | 901 | err: &mut Diagnostic, |
5869c6ff | 902 | ) { |
6a06907d | 903 | let tables = tcx.typeck(closure_local_def_id); |
9ffffee4 | 904 | if let Some((span, closure_kind_origin)) = tcx.closure_kind_origin(closure_local_def_id) { |
6a06907d XL |
905 | let reason = if let PlaceBase::Upvar(upvar_id) = closure_kind_origin.base { |
906 | let upvar = ty::place_to_string_for_capture(tcx, closure_kind_origin); | |
907 | let root_hir_id = upvar_id.var_path.hir_id; | |
94222f64 | 908 | // we have an origin for this closure kind starting at this root variable so it's safe to unwrap here |
064997fb FG |
909 | let captured_places = |
910 | tables.closure_min_captures[&closure_local_def_id].get(&root_hir_id).unwrap(); | |
6a06907d XL |
911 | |
912 | let origin_projection = closure_kind_origin | |
913 | .projections | |
914 | .iter() | |
915 | .map(|proj| proj.kind) | |
916 | .collect::<Vec<_>>(); | |
917 | let mut capture_reason = String::new(); | |
918 | for captured_place in captured_places { | |
919 | let captured_place_kinds = captured_place | |
920 | .place | |
921 | .projections | |
922 | .iter() | |
923 | .map(|proj| proj.kind) | |
924 | .collect::<Vec<_>>(); | |
925 | if rustc_middle::ty::is_ancestor_or_same_capture( | |
926 | &captured_place_kinds, | |
927 | &origin_projection, | |
928 | ) { | |
929 | match captured_place.info.capture_kind { | |
5099ac24 FG |
930 | ty::UpvarCapture::ByRef( |
931 | ty::BorrowKind::MutBorrow | ty::BorrowKind::UniqueImmBorrow, | |
932 | ) => { | |
04454e1e | 933 | capture_reason = format!("mutable borrow of `{upvar}`"); |
6a06907d | 934 | } |
5099ac24 | 935 | ty::UpvarCapture::ByValue => { |
04454e1e | 936 | capture_reason = format!("possible mutation of `{upvar}`"); |
6a06907d | 937 | } |
04454e1e | 938 | _ => bug!("upvar `{upvar}` borrowed, but not mutably"), |
6a06907d XL |
939 | } |
940 | break; | |
941 | } | |
5869c6ff | 942 | } |
6a06907d | 943 | if capture_reason.is_empty() { |
04454e1e | 944 | bug!("upvar `{upvar}` borrowed, but cannot find reason"); |
6a06907d XL |
945 | } |
946 | capture_reason | |
947 | } else { | |
948 | bug!("not an upvar") | |
949 | }; | |
950 | err.span_label( | |
951 | *span, | |
952 | format!( | |
953 | "calling `{}` requires mutable binding due to {}", | |
954 | self.describe_place(the_place_err).unwrap(), | |
955 | reason | |
956 | ), | |
957 | ); | |
958 | } | |
959 | } | |
960 | ||
961 | // Attempt to search similar mutable associated items for suggestion. | |
962 | // In the future, attempt in all path but initially for RHS of for_loop | |
5e7ed085 | 963 | fn suggest_similar_mut_method_for_for_loop(&self, err: &mut Diagnostic) { |
6a06907d XL |
964 | use hir::{ |
965 | BodyId, Expr, | |
966 | ExprKind::{Block, Call, DropTemps, Match, MethodCall}, | |
967 | HirId, ImplItem, ImplItemKind, Item, ItemKind, | |
968 | }; | |
969 | ||
5099ac24 | 970 | fn maybe_body_id_of_fn(hir_map: Map<'_>, id: HirId) -> Option<BodyId> { |
6a06907d XL |
971 | match hir_map.find(id) { |
972 | Some(Node::Item(Item { kind: ItemKind::Fn(_, _, body_id), .. })) | |
973 | | Some(Node::ImplItem(ImplItem { kind: ImplItemKind::Fn(_, body_id), .. })) => { | |
974 | Some(*body_id) | |
975 | } | |
976 | _ => None, | |
977 | } | |
978 | } | |
979 | let hir_map = self.infcx.tcx.hir(); | |
980 | let mir_body_hir_id = self.mir_hir_id(); | |
5099ac24 | 981 | if let Some(fn_body_id) = maybe_body_id_of_fn(hir_map, mir_body_hir_id) { |
6a06907d XL |
982 | if let Block( |
983 | hir::Block { | |
984 | expr: | |
985 | Some(Expr { | |
986 | kind: | |
987 | DropTemps(Expr { | |
988 | kind: | |
989 | Match( | |
990 | Expr { | |
991 | kind: | |
992 | Call( | |
993 | _, | |
a2a8927a XL |
994 | [ |
995 | Expr { | |
5e7ed085 | 996 | kind: |
f2b60f7d | 997 | MethodCall(path_segment, _, _, span), |
a2a8927a XL |
998 | hir_id, |
999 | .. | |
1000 | }, | |
1001 | .., | |
1002 | ], | |
6a06907d XL |
1003 | ), |
1004 | .. | |
1005 | }, | |
1006 | .., | |
1007 | ), | |
1008 | .. | |
1009 | }), | |
1010 | .. | |
1011 | }), | |
1012 | .. | |
1013 | }, | |
1014 | _, | |
1015 | ) = hir_map.body(fn_body_id).value.kind | |
1016 | { | |
f2b60f7d FG |
1017 | let opt_suggestions = self |
1018 | .infcx | |
1019 | .tcx | |
2b03887a | 1020 | .typeck(path_segment.hir_id.owner.def_id) |
f2b60f7d | 1021 | .type_dependent_def_id(*hir_id) |
6a06907d XL |
1022 | .and_then(|def_id| self.infcx.tcx.impl_of_method(def_id)) |
1023 | .map(|def_id| self.infcx.tcx.associated_items(def_id)) | |
1024 | .map(|assoc_items| { | |
1025 | assoc_items | |
1026 | .in_definition_order() | |
5099ac24 | 1027 | .map(|assoc_item_def| assoc_item_def.ident(self.infcx.tcx)) |
6a06907d XL |
1028 | .filter(|&ident| { |
1029 | let original_method_ident = path_segment.ident; | |
1030 | original_method_ident != ident | |
1031 | && ident | |
1032 | .as_str() | |
1033 | .starts_with(&original_method_ident.name.to_string()) | |
1034 | }) | |
04454e1e | 1035 | .map(|ident| format!("{ident}()")) |
36d6ef2b | 1036 | .peekable() |
6a06907d XL |
1037 | }); |
1038 | ||
04454e1e FG |
1039 | if let Some(mut suggestions) = opt_suggestions |
1040 | && suggestions.peek().is_some() | |
1041 | { | |
1042 | err.span_suggestions( | |
1043 | *span, | |
1044 | "use mutable method", | |
1045 | suggestions, | |
1046 | Applicability::MaybeIncorrect, | |
1047 | ); | |
5869c6ff | 1048 | } |
5869c6ff | 1049 | } |
5869c6ff | 1050 | }; |
5869c6ff XL |
1051 | } |
1052 | ||
74b04a01 | 1053 | /// Targeted error when encountering an `FnMut` closure where an `Fn` closure was expected. |
5e7ed085 | 1054 | fn expected_fn_found_fn_mut_call(&self, err: &mut Diagnostic, sp: Span, act: &str) { |
04454e1e | 1055 | err.span_label(sp, format!("cannot {act}")); |
74b04a01 XL |
1056 | |
1057 | let hir = self.infcx.tcx.hir(); | |
29967ef6 | 1058 | let closure_id = self.mir_hir_id(); |
f2b60f7d | 1059 | let closure_span = self.infcx.tcx.def_span(self.mir_def_id()); |
9c376795 | 1060 | let fn_call_id = hir.parent_id(closure_id); |
74b04a01 | 1061 | let node = hir.get(fn_call_id); |
064997fb | 1062 | let def_id = hir.enclosing_body_owner(fn_call_id); |
74b04a01 XL |
1063 | let mut look_at_return = true; |
1064 | // If we can detect the expression to be an `fn` call where the closure was an argument, | |
1065 | // we point at the `fn` definition argument... | |
ba9703b0 XL |
1066 | if let hir::Node::Expr(hir::Expr { kind: hir::ExprKind::Call(func, args), .. }) = node { |
1067 | let arg_pos = args | |
1068 | .iter() | |
1069 | .enumerate() | |
064997fb | 1070 | .filter(|(_, arg)| arg.hir_id == closure_id) |
ba9703b0 XL |
1071 | .map(|(pos, _)| pos) |
1072 | .next(); | |
3dfed10e | 1073 | let tables = self.infcx.tcx.typeck(def_id); |
ba9703b0 | 1074 | if let Some(ty::FnDef(def_id, _)) = |
1b1a35ee | 1075 | tables.node_type_opt(func.hir_id).as_ref().map(|ty| ty.kind()) |
ba9703b0 XL |
1076 | { |
1077 | let arg = match hir.get_if_local(*def_id) { | |
1078 | Some( | |
1079 | hir::Node::Item(hir::Item { | |
1080 | ident, kind: hir::ItemKind::Fn(sig, ..), .. | |
1081 | }) | |
1082 | | hir::Node::TraitItem(hir::TraitItem { | |
74b04a01 | 1083 | ident, |
ba9703b0 | 1084 | kind: hir::TraitItemKind::Fn(sig, _), |
74b04a01 | 1085 | .. |
ba9703b0 XL |
1086 | }) |
1087 | | hir::Node::ImplItem(hir::ImplItem { | |
74b04a01 | 1088 | ident, |
ba9703b0 | 1089 | kind: hir::ImplItemKind::Fn(sig, _), |
74b04a01 | 1090 | .. |
ba9703b0 XL |
1091 | }), |
1092 | ) => Some( | |
1093 | arg_pos | |
1094 | .and_then(|pos| { | |
1095 | sig.decl.inputs.get( | |
1096 | pos + if sig.decl.implicit_self.has_implicit_self() { | |
1097 | 1 | |
1098 | } else { | |
1099 | 0 | |
1100 | }, | |
1101 | ) | |
1102 | }) | |
1103 | .map(|arg| arg.span) | |
1104 | .unwrap_or(ident.span), | |
1105 | ), | |
1106 | _ => None, | |
1107 | }; | |
1108 | if let Some(span) = arg { | |
1109 | err.span_label(span, "change this to accept `FnMut` instead of `Fn`"); | |
1110 | err.span_label(func.span, "expects `Fn` instead of `FnMut`"); | |
f2b60f7d | 1111 | err.span_label(closure_span, "in this closure"); |
ba9703b0 | 1112 | look_at_return = false; |
74b04a01 XL |
1113 | } |
1114 | } | |
74b04a01 | 1115 | } |
ba9703b0 | 1116 | |
74b04a01 XL |
1117 | if look_at_return && hir.get_return_block(closure_id).is_some() { |
1118 | // ...otherwise we are probably in the tail expression of the function, point at the | |
1119 | // return type. | |
2b03887a | 1120 | match hir.get_by_def_id(hir.get_parent_item(fn_call_id).def_id) { |
74b04a01 XL |
1121 | hir::Node::Item(hir::Item { ident, kind: hir::ItemKind::Fn(sig, ..), .. }) |
1122 | | hir::Node::TraitItem(hir::TraitItem { | |
1123 | ident, | |
ba9703b0 | 1124 | kind: hir::TraitItemKind::Fn(sig, _), |
74b04a01 XL |
1125 | .. |
1126 | }) | |
1127 | | hir::Node::ImplItem(hir::ImplItem { | |
1128 | ident, | |
ba9703b0 | 1129 | kind: hir::ImplItemKind::Fn(sig, _), |
74b04a01 XL |
1130 | .. |
1131 | }) => { | |
1132 | err.span_label(ident.span, ""); | |
1133 | err.span_label( | |
1134 | sig.decl.output.span(), | |
1135 | "change this to return `FnMut` instead of `Fn`", | |
1136 | ); | |
f2b60f7d | 1137 | err.span_label(closure_span, "in this closure"); |
74b04a01 XL |
1138 | } |
1139 | _ => {} | |
1140 | } | |
1141 | } | |
1142 | } | |
b7449926 | 1143 | } |
8faf50e0 | 1144 | |
9c376795 | 1145 | pub fn mut_borrow_of_mutable_ref(local_decl: &LocalDecl<'_>, local_name: Option<Symbol>) -> bool { |
29967ef6 XL |
1146 | debug!("local_info: {:?}, ty.kind(): {:?}", local_decl.local_info, local_decl.ty.kind()); |
1147 | ||
1148 | match local_decl.local_info.as_deref() { | |
1149 | // Check if mutably borrowing a mutable reference. | |
1150 | Some(LocalInfo::User(ClearCrossCrate::Set(mir::BindingForm::Var( | |
1151 | mir::VarBindingForm { | |
1152 | binding_mode: ty::BindingMode::BindByValue(Mutability::Not), .. | |
1153 | }, | |
1154 | )))) => matches!(local_decl.ty.kind(), ty::Ref(_, _, hir::Mutability::Mut)), | |
1155 | Some(LocalInfo::User(ClearCrossCrate::Set(mir::BindingForm::ImplicitSelf(kind)))) => { | |
1156 | // Check if the user variable is a `&mut self` and we can therefore | |
1157 | // suggest removing the `&mut`. | |
1158 | // | |
1159 | // Deliberately fall into this case for all implicit self types, | |
1160 | // so that we don't fall in to the next case with them. | |
f2b60f7d | 1161 | *kind == hir::ImplicitSelfKind::MutRef |
29967ef6 XL |
1162 | } |
1163 | _ if Some(kw::SelfLower) == local_name => { | |
1164 | // Otherwise, check if the name is the `self` keyword - in which case | |
1165 | // we have an explicit self. Do the same thing in this case and check | |
1166 | // for a `self: &mut Self` to suggest removing the `&mut`. | |
1167 | matches!(local_decl.ty.kind(), ty::Ref(_, _, hir::Mutability::Mut)) | |
1168 | } | |
1169 | _ => false, | |
1170 | } | |
1171 | } | |
1172 | ||
dc9dc135 XL |
1173 | fn suggest_ampmut_self<'tcx>( |
1174 | tcx: TyCtxt<'tcx>, | |
b7449926 XL |
1175 | local_decl: &mir::LocalDecl<'tcx>, |
1176 | ) -> (Span, String) { | |
1177 | let sp = local_decl.source_info.span; | |
dfeec247 XL |
1178 | ( |
1179 | sp, | |
1180 | match tcx.sess.source_map().span_to_snippet(sp) { | |
1181 | Ok(snippet) => { | |
1182 | let lt_pos = snippet.find('\''); | |
1183 | if let Some(lt_pos) = lt_pos { | |
1184 | format!("&{}mut self", &snippet[lt_pos..snippet.len() - 4]) | |
1185 | } else { | |
1186 | "&mut self".to_string() | |
1187 | } | |
8faf50e0 | 1188 | } |
dfeec247 XL |
1189 | _ => "&mut self".to_string(), |
1190 | }, | |
1191 | ) | |
8faf50e0 XL |
1192 | } |
1193 | ||
1194 | // When we want to suggest a user change a local variable to be a `&mut`, there | |
1195 | // are three potential "obvious" things to highlight: | |
1196 | // | |
1197 | // let ident [: Type] [= RightHandSideExpression]; | |
1198 | // ^^^^^ ^^^^ ^^^^^^^^^^^^^^^^^^^^^^^ | |
1199 | // (1.) (2.) (3.) | |
1200 | // | |
1201 | // We can always fallback on highlighting the first. But chances are good that | |
1202 | // the user experience will be better if we highlight one of the others if possible; | |
1203 | // for example, if the RHS is present and the Type is not, then the type is going to | |
1204 | // be inferred *from* the RHS, which means we should highlight that (and suggest | |
1205 | // that they borrow the RHS mutably). | |
1206 | // | |
1207 | // This implementation attempts to emulate AST-borrowck prioritization | |
1208 | // by trying (3.), then (2.) and finally falling back on (1.). | |
dc9dc135 XL |
1209 | fn suggest_ampmut<'tcx>( |
1210 | tcx: TyCtxt<'tcx>, | |
8faf50e0 | 1211 | local_decl: &mir::LocalDecl<'tcx>, |
ba9703b0 | 1212 | opt_assignment_rhs_span: Option<Span>, |
8faf50e0 | 1213 | opt_ty_info: Option<Span>, |
064997fb | 1214 | ) -> (bool, Span, String) { |
04454e1e FG |
1215 | if let Some(assignment_rhs_span) = opt_assignment_rhs_span |
1216 | && let Ok(src) = tcx.sess.source_map().span_to_snippet(assignment_rhs_span) | |
1217 | { | |
1218 | let is_mutbl = |ty: &str| -> bool { | |
1219 | if let Some(rest) = ty.strip_prefix("mut") { | |
1220 | match rest.chars().next() { | |
1221 | // e.g. `&mut x` | |
1222 | Some(c) if c.is_whitespace() => true, | |
1223 | // e.g. `&mut(x)` | |
1224 | Some('(') => true, | |
1225 | // e.g. `&mut{x}` | |
1226 | Some('{') => true, | |
1227 | // e.g. `&mutablevar` | |
1228 | _ => false, | |
17df50a5 | 1229 | } |
04454e1e FG |
1230 | } else { |
1231 | false | |
1232 | } | |
1233 | }; | |
1234 | if let (true, Some(ws_pos)) = (src.starts_with("&'"), src.find(char::is_whitespace)) { | |
1235 | let lt_name = &src[1..ws_pos]; | |
1236 | let ty = src[ws_pos..].trim_start(); | |
1237 | if !is_mutbl(ty) { | |
064997fb | 1238 | return (true, assignment_rhs_span, format!("&{lt_name} mut {ty}")); |
04454e1e FG |
1239 | } |
1240 | } else if let Some(stripped) = src.strip_prefix('&') { | |
1241 | let stripped = stripped.trim_start(); | |
1242 | if !is_mutbl(stripped) { | |
064997fb | 1243 | return (true, assignment_rhs_span, format!("&mut {stripped}")); |
8faf50e0 XL |
1244 | } |
1245 | } | |
1246 | } | |
1247 | ||
064997fb | 1248 | let (suggestability, highlight_span) = match opt_ty_info { |
8faf50e0 XL |
1249 | // if this is a variable binding with an explicit type, |
1250 | // try to highlight that for the suggestion. | |
064997fb | 1251 | Some(ty_span) => (true, ty_span), |
8faf50e0 XL |
1252 | |
1253 | // otherwise, just highlight the span associated with | |
1254 | // the (MIR) LocalDecl. | |
064997fb | 1255 | None => (false, local_decl.source_info.span), |
8faf50e0 XL |
1256 | }; |
1257 | ||
04454e1e FG |
1258 | if let Ok(src) = tcx.sess.source_map().span_to_snippet(highlight_span) |
1259 | && let (true, Some(ws_pos)) = (src.starts_with("&'"), src.find(char::is_whitespace)) | |
1260 | { | |
1261 | let lt_name = &src[1..ws_pos]; | |
1262 | let ty = &src[ws_pos..]; | |
9c376795 | 1263 | return (true, highlight_span, format!("&{lt_name} mut{ty}")); |
b7449926 XL |
1264 | } |
1265 | ||
8faf50e0 | 1266 | let ty_mut = local_decl.ty.builtin_deref(true).unwrap(); |
dfeec247 XL |
1267 | assert_eq!(ty_mut.mutbl, hir::Mutability::Not); |
1268 | ( | |
064997fb | 1269 | suggestability, |
dfeec247 XL |
1270 | highlight_span, |
1271 | if local_decl.ty.is_region_ptr() { | |
1272 | format!("&mut {}", ty_mut.ty) | |
1273 | } else { | |
1274 | format!("*mut {}", ty_mut.ty) | |
1275 | }, | |
1276 | ) | |
8faf50e0 XL |
1277 | } |
1278 | ||
48663c56 | 1279 | fn is_closure_or_generator(ty: Ty<'_>) -> bool { |
8faf50e0 XL |
1280 | ty.is_closure() || ty.is_generator() |
1281 | } | |
0bf4aa26 | 1282 | |
a2a8927a XL |
1283 | /// Given a field that needs to be mutable, returns a span where the " mut " could go. |
1284 | /// This function expects the local to be a reference to a struct in order to produce a span. | |
0bf4aa26 XL |
1285 | /// |
1286 | /// ```text | |
a2a8927a XL |
1287 | /// LL | s: &'a String |
1288 | /// | ^^^ returns a span taking up the space here | |
0bf4aa26 | 1289 | /// ``` |
a2a8927a | 1290 | fn get_mut_span_in_struct_field<'tcx>( |
dc9dc135 | 1291 | tcx: TyCtxt<'tcx>, |
48663c56 | 1292 | ty: Ty<'tcx>, |
5e7ed085 | 1293 | field: mir::Field, |
a2a8927a | 1294 | ) -> Option<Span> { |
0bf4aa26 | 1295 | // Expect our local to be a reference to a struct of some kind. |
5e7ed085 FG |
1296 | if let ty::Ref(_, ty, _) = ty.kind() |
1297 | && let ty::Adt(def, _) = ty.kind() | |
1298 | && let field = def.all_fields().nth(field.index())? | |
1299 | // Use the HIR types to construct the diagnostic message. | |
1300 | && let node = tcx.hir().find_by_def_id(field.did.as_local()?)? | |
1301 | // Now we're dealing with the actual struct that we're going to suggest a change to, | |
1302 | // we can expect a field that is an immutable reference to a type. | |
1303 | && let hir::Node::Field(field) = node | |
9c376795 | 1304 | && let hir::TyKind::Ref(lt, hir::MutTy { mutbl: hir::Mutability::Not, ty }) = field.ty.kind |
5e7ed085 | 1305 | { |
487cf647 | 1306 | return Some(lt.ident.span.between(ty.span)); |
0bf4aa26 XL |
1307 | } |
1308 | ||
1309 | None | |
1310 | } | |
416331ca XL |
1311 | |
1312 | /// If possible, suggest replacing `ref` with `ref mut`. | |
e1599b0c XL |
1313 | fn suggest_ref_mut(tcx: TyCtxt<'_>, binding_span: Span) -> Option<String> { |
1314 | let hi_src = tcx.sess.source_map().span_to_snippet(binding_span).ok()?; | |
dfeec247 | 1315 | if hi_src.starts_with("ref") && hi_src["ref".len()..].starts_with(rustc_lexer::is_whitespace) { |
416331ca XL |
1316 | let replacement = format!("ref mut{}", &hi_src["ref".len()..]); |
1317 | Some(replacement) | |
1318 | } else { | |
1319 | None | |
1320 | } | |
1321 | } |