]>
Commit | Line | Data |
---|---|---|
dfeec247 XL |
1 | use rustc_hir as hir; |
2 | use rustc_hir::Node; | |
6a06907d | 3 | use rustc_middle::hir::map::Map; |
ba9703b0 XL |
4 | use rustc_middle::mir::{Mutability, Place, PlaceRef, ProjectionElem}; |
5 | use rustc_middle::ty::{self, Ty, TyCtxt}; | |
5869c6ff XL |
6 | use rustc_middle::{ |
7 | hir::place::PlaceBase, | |
17df50a5 | 8 | mir::{self, ClearCrossCrate, Local, LocalDecl, LocalInfo, LocalKind, Location}, |
5869c6ff | 9 | }; |
ba9703b0 | 10 | use rustc_span::source_map::DesugaringKind; |
29967ef6 | 11 | use rustc_span::symbol::{kw, Symbol}; |
dfeec247 | 12 | use rustc_span::Span; |
8faf50e0 | 13 | |
60c5eb7d | 14 | use crate::borrow_check::diagnostics::BorrowedContentSource; |
dfeec247 | 15 | use crate::borrow_check::MirBorrowckCtxt; |
9fa01778 | 16 | use crate::util::collect_writes::FindAssignments; |
74b04a01 | 17 | use rustc_errors::{Applicability, DiagnosticBuilder}; |
8faf50e0 | 18 | |
b7449926 | 19 | #[derive(Copy, Clone, Debug, Eq, PartialEq)] |
60c5eb7d | 20 | pub(crate) enum AccessKind { |
8faf50e0 XL |
21 | MutableBorrow, |
22 | Mutate, | |
8faf50e0 XL |
23 | } |
24 | ||
dc9dc135 | 25 | impl<'a, 'tcx> MirBorrowckCtxt<'a, 'tcx> { |
60c5eb7d | 26 | pub(crate) fn report_mutability_error( |
8faf50e0 | 27 | &mut self, |
ba9703b0 | 28 | access_place: Place<'tcx>, |
8faf50e0 | 29 | span: Span, |
74b04a01 | 30 | the_place_err: PlaceRef<'tcx>, |
8faf50e0 XL |
31 | error_access: AccessKind, |
32 | location: Location, | |
33 | ) { | |
b7449926 XL |
34 | debug!( |
35 | "report_mutability_error(\ | |
36 | access_place={:?}, span={:?}, the_place_err={:?}, error_access={:?}, location={:?},\ | |
37 | )", | |
38 | access_place, span, the_place_err, error_access, location, | |
39 | ); | |
40 | ||
8faf50e0 XL |
41 | let mut err; |
42 | let item_msg; | |
43 | let reason; | |
416331ca XL |
44 | let mut opt_source = None; |
45 | let access_place_desc = self.describe_place(access_place.as_ref()); | |
b7449926 | 46 | debug!("report_mutability_error: access_place_desc={:?}", access_place_desc); |
8faf50e0 XL |
47 | |
48 | match the_place_err { | |
dfeec247 | 49 | PlaceRef { local, projection: [] } => { |
8faf50e0 | 50 | item_msg = format!("`{}`", access_place_desc.unwrap()); |
e74abb32 | 51 | if access_place.as_local().is_some() { |
8faf50e0 XL |
52 | reason = ", as it is not declared as mutable".to_string(); |
53 | } else { | |
74b04a01 | 54 | let name = self.local_names[local].expect("immutable unnamed local"); |
8faf50e0 XL |
55 | reason = format!(", as `{}` is not declared as mutable", name); |
56 | } | |
57 | } | |
58 | ||
416331ca | 59 | PlaceRef { |
dfeec247 | 60 | local, |
e1599b0c | 61 | projection: [proj_base @ .., ProjectionElem::Field(upvar_index, _)], |
416331ca | 62 | } => { |
8faf50e0 | 63 | debug_assert!(is_closure_or_generator( |
f9f354fc | 64 | Place::ty_from(local, proj_base, self.body, self.infcx.tcx).ty |
dfeec247 | 65 | )); |
8faf50e0 | 66 | |
5869c6ff XL |
67 | let imm_borrow_derefed = self.upvars[upvar_index.index()] |
68 | .place | |
69 | .place | |
70 | .deref_tys() | |
71 | .any(|ty| matches!(ty.kind(), ty::Ref(.., hir::Mutability::Not))); | |
72 | ||
73 | // If the place is immutable then: | |
74 | // | |
75 | // - Either we deref a immutable ref to get to our final place. | |
76 | // - We don't capture derefs of raw ptrs | |
77 | // - Or the final place is immut because the root variable of the capture | |
78 | // isn't marked mut and we should suggest that to the user. | |
79 | if imm_borrow_derefed { | |
80 | // If we deref an immutable ref then the suggestion here doesn't help. | |
81 | return; | |
8faf50e0 | 82 | } else { |
5869c6ff XL |
83 | item_msg = format!("`{}`", access_place_desc.unwrap()); |
84 | if self.is_upvar_field_projection(access_place.as_ref()).is_some() { | |
85 | reason = ", as it is not declared as mutable".to_string(); | |
86 | } else { | |
17df50a5 | 87 | let name = self.upvars[upvar_index.index()].place.to_string(self.infcx.tcx); |
5869c6ff XL |
88 | reason = format!(", as `{}` is not declared as mutable", name); |
89 | } | |
8faf50e0 XL |
90 | } |
91 | } | |
92 | ||
dfeec247 | 93 | PlaceRef { local, projection: [ProjectionElem::Deref] } |
74b04a01 | 94 | if self.body.local_decls[local].is_ref_for_guard() => |
dfeec247 | 95 | { |
60c5eb7d XL |
96 | item_msg = format!("`{}`", access_place_desc.unwrap()); |
97 | reason = ", as it is immutable for the pattern guard".to_string(); | |
98 | } | |
dfeec247 | 99 | PlaceRef { local, projection: [ProjectionElem::Deref] } |
74b04a01 | 100 | if self.body.local_decls[local].is_ref_to_static() => |
dfeec247 | 101 | { |
60c5eb7d XL |
102 | if access_place.projection.len() == 1 { |
103 | item_msg = format!("immutable static item `{}`", access_place_desc.unwrap()); | |
104 | reason = String::new(); | |
105 | } else { | |
106 | item_msg = format!("`{}`", access_place_desc.unwrap()); | |
74b04a01 | 107 | let local_info = &self.body.local_decls[local].local_info; |
f9f354fc | 108 | if let Some(box LocalInfo::StaticRef { def_id, .. }) = *local_info { |
60c5eb7d XL |
109 | let static_name = &self.infcx.tcx.item_name(def_id); |
110 | reason = format!(", as `{}` is an immutable static item", static_name); | |
111 | } else { | |
112 | bug!("is_ref_to_static return true, but not ref to static?"); | |
113 | } | |
114 | } | |
115 | } | |
dfeec247 | 116 | PlaceRef { local: _, projection: [proj_base @ .., ProjectionElem::Deref] } => { |
17df50a5 | 117 | if the_place_err.local == ty::CAPTURE_STRUCT_LOCAL |
dfeec247 XL |
118 | && proj_base.is_empty() |
119 | && !self.upvars.is_empty() | |
120 | { | |
8faf50e0 | 121 | item_msg = format!("`{}`", access_place_desc.unwrap()); |
17df50a5 XL |
122 | debug_assert!( |
123 | self.body.local_decls[ty::CAPTURE_STRUCT_LOCAL].ty.is_region_ptr() | |
124 | ); | |
8faf50e0 | 125 | debug_assert!(is_closure_or_generator( |
416331ca | 126 | Place::ty_from( |
dfeec247 | 127 | the_place_err.local, |
416331ca | 128 | the_place_err.projection, |
f9f354fc | 129 | self.body, |
416331ca XL |
130 | self.infcx.tcx |
131 | ) | |
132 | .ty | |
8faf50e0 XL |
133 | )); |
134 | ||
dfeec247 XL |
135 | reason = if self.is_upvar_field_projection(access_place.as_ref()).is_some() { |
136 | ", as it is a captured variable in a `Fn` closure".to_string() | |
137 | } else { | |
138 | ", as `Fn` closures cannot mutate their captured variables".to_string() | |
139 | } | |
8faf50e0 | 140 | } else { |
416331ca | 141 | let source = self.borrowed_content_source(PlaceRef { |
dfeec247 | 142 | local: the_place_err.local, |
e1599b0c | 143 | projection: proj_base, |
416331ca | 144 | }); |
ba9703b0 | 145 | let pointer_type = source.describe_for_immutable_place(self.infcx.tcx); |
416331ca | 146 | opt_source = Some(source); |
8faf50e0 XL |
147 | if let Some(desc) = access_place_desc { |
148 | item_msg = format!("`{}`", desc); | |
149 | reason = match error_access { | |
416331ca | 150 | AccessKind::Mutate => format!(" which is behind {}", pointer_type), |
8faf50e0 | 151 | AccessKind::MutableBorrow => { |
416331ca | 152 | format!(", as it is behind {}", pointer_type) |
8faf50e0 XL |
153 | } |
154 | } | |
155 | } else { | |
416331ca | 156 | item_msg = format!("data in {}", pointer_type); |
b7449926 | 157 | reason = String::new(); |
8faf50e0 XL |
158 | } |
159 | } | |
160 | } | |
161 | ||
ba9703b0 XL |
162 | PlaceRef { |
163 | local: _, | |
164 | projection: | |
165 | [.., ProjectionElem::Index(_) | |
166 | | ProjectionElem::ConstantIndex { .. } | |
167 | | ProjectionElem::Subslice { .. } | |
168 | | ProjectionElem::Downcast(..)], | |
169 | } => bug!("Unexpected immutable place."), | |
8faf50e0 XL |
170 | } |
171 | ||
b7449926 XL |
172 | debug!("report_mutability_error: item_msg={:?}, reason={:?}", item_msg, reason); |
173 | ||
8faf50e0 XL |
174 | // `act` and `acted_on` are strings that let us abstract over |
175 | // the verbs used in some diagnostic messages. | |
176 | let act; | |
177 | let acted_on; | |
178 | ||
8faf50e0 | 179 | let span = match error_access { |
8faf50e0 | 180 | AccessKind::Mutate => { |
416331ca | 181 | err = self.cannot_assign(span, &(item_msg + &reason)); |
8faf50e0 XL |
182 | act = "assign"; |
183 | acted_on = "written"; | |
184 | span | |
185 | } | |
186 | AccessKind::MutableBorrow => { | |
187 | act = "borrow as mutable"; | |
188 | acted_on = "borrowed as mutable"; | |
189 | ||
b7449926 XL |
190 | let borrow_spans = self.borrow_spans(span, location); |
191 | let borrow_span = borrow_spans.args_or_use(); | |
dfeec247 | 192 | err = self.cannot_borrow_path_as_mutable_because(borrow_span, &item_msg, &reason); |
b7449926 XL |
193 | borrow_spans.var_span_label( |
194 | &mut err, | |
195 | format!( | |
ba9703b0 XL |
196 | "mutable borrow occurs due to use of {} in closure", |
197 | self.describe_any_place(access_place.as_ref()), | |
dfeec247 | 198 | ), |
17df50a5 | 199 | "mutable", |
b7449926 XL |
200 | ); |
201 | borrow_span | |
8faf50e0 XL |
202 | } |
203 | }; | |
204 | ||
b7449926 XL |
205 | debug!("report_mutability_error: act={:?}, acted_on={:?}", act, acted_on); |
206 | ||
8faf50e0 | 207 | match the_place_err { |
0bf4aa26 XL |
208 | // Suggest making an existing shared borrow in a struct definition a mutable borrow. |
209 | // | |
210 | // This is applicable when we have a deref of a field access to a deref of a local - | |
211 | // something like `*((*_1).0`. The local that we get will be a reference to the | |
212 | // struct we've got a field access of (it must be a reference since there's a deref | |
213 | // after the field access). | |
416331ca | 214 | PlaceRef { |
dfeec247 XL |
215 | local, |
216 | projection: | |
217 | [proj_base @ .., ProjectionElem::Deref, ProjectionElem::Field(field, _), ProjectionElem::Deref], | |
416331ca | 218 | } => { |
0bf4aa26 XL |
219 | err.span_label(span, format!("cannot {ACT}", ACT = act)); |
220 | ||
221 | if let Some((span, message)) = annotate_struct_field( | |
222 | self.infcx.tcx, | |
f9f354fc | 223 | Place::ty_from(local, proj_base, self.body, self.infcx.tcx).ty, |
0bf4aa26 XL |
224 | field, |
225 | ) { | |
9fa01778 | 226 | err.span_suggestion( |
0bf4aa26 XL |
227 | span, |
228 | "consider changing this to be mutable", | |
229 | message, | |
230 | Applicability::MaybeIncorrect, | |
231 | ); | |
232 | } | |
dfeec247 | 233 | } |
0bf4aa26 XL |
234 | |
235 | // Suggest removing a `&mut` from the use of a mutable reference. | |
dfeec247 | 236 | PlaceRef { local, projection: [] } |
29967ef6 XL |
237 | if self |
238 | .body | |
239 | .local_decls | |
240 | .get(local) | |
241 | .map(|l| mut_borrow_of_mutable_ref(l, self.local_names[local])) | |
242 | .unwrap_or(false) => | |
dfeec247 | 243 | { |
0bf4aa26 XL |
244 | err.span_label(span, format!("cannot {ACT}", ACT = act)); |
245 | err.span_label(span, "try removing `&mut` here"); | |
dfeec247 | 246 | } |
0bf4aa26 | 247 | |
8faf50e0 XL |
248 | // We want to suggest users use `let mut` for local (user |
249 | // variable) mutations... | |
dfeec247 | 250 | PlaceRef { local, projection: [] } |
74b04a01 | 251 | if self.body.local_decls[local].can_be_made_mutable() => |
dfeec247 | 252 | { |
8faf50e0 XL |
253 | // ... but it doesn't make sense to suggest it on |
254 | // variables that are `ref x`, `ref mut x`, `&self`, | |
255 | // or `&mut self` (such variables are simply not | |
256 | // mutable). | |
74b04a01 | 257 | let local_decl = &self.body.local_decls[local]; |
8faf50e0 XL |
258 | assert_eq!(local_decl.mutability, Mutability::Not); |
259 | ||
260 | err.span_label(span, format!("cannot {ACT}", ACT = act)); | |
9fa01778 | 261 | err.span_suggestion( |
8faf50e0 XL |
262 | local_decl.source_info.span, |
263 | "consider changing this to be mutable", | |
74b04a01 | 264 | format!("mut {}", self.local_names[local].unwrap()), |
0bf4aa26 | 265 | Applicability::MachineApplicable, |
8faf50e0 | 266 | ); |
5869c6ff XL |
267 | let tcx = self.infcx.tcx; |
268 | if let ty::Closure(id, _) = the_place_err.ty(self.body, tcx).ty.kind() { | |
269 | self.show_mutating_upvar(tcx, id, the_place_err, &mut err); | |
270 | } | |
8faf50e0 XL |
271 | } |
272 | ||
273 | // Also suggest adding mut for upvars | |
416331ca | 274 | PlaceRef { |
dfeec247 | 275 | local, |
e1599b0c | 276 | projection: [proj_base @ .., ProjectionElem::Field(upvar_index, _)], |
416331ca | 277 | } => { |
8faf50e0 | 278 | debug_assert!(is_closure_or_generator( |
f9f354fc | 279 | Place::ty_from(local, proj_base, self.body, self.infcx.tcx).ty |
8faf50e0 XL |
280 | )); |
281 | ||
5869c6ff XL |
282 | let captured_place = &self.upvars[upvar_index.index()].place; |
283 | ||
8faf50e0 XL |
284 | err.span_label(span, format!("cannot {ACT}", ACT = act)); |
285 | ||
5869c6ff XL |
286 | let upvar_hir_id = captured_place.get_root_variable(); |
287 | ||
dfeec247 | 288 | if let Some(Node::Binding(pat)) = self.infcx.tcx.hir().find(upvar_hir_id) { |
8faf50e0 XL |
289 | if let hir::PatKind::Binding( |
290 | hir::BindingAnnotation::Unannotated, | |
291 | _, | |
292 | upvar_ident, | |
293 | _, | |
e74abb32 | 294 | ) = pat.kind |
8faf50e0 | 295 | { |
9fa01778 | 296 | err.span_suggestion( |
8faf50e0 XL |
297 | upvar_ident.span, |
298 | "consider changing this to be mutable", | |
299 | format!("mut {}", upvar_ident.name), | |
0bf4aa26 | 300 | Applicability::MachineApplicable, |
8faf50e0 XL |
301 | ); |
302 | } | |
303 | } | |
5869c6ff XL |
304 | |
305 | let tcx = self.infcx.tcx; | |
306 | if let ty::Ref(_, ty, Mutability::Mut) = the_place_err.ty(self.body, tcx).ty.kind() | |
307 | { | |
308 | if let ty::Closure(id, _) = ty.kind() { | |
309 | self.show_mutating_upvar(tcx, id, the_place_err, &mut err); | |
310 | } | |
311 | } | |
8faf50e0 XL |
312 | } |
313 | ||
314 | // complete hack to approximate old AST-borrowck | |
315 | // diagnostic: if the span starts with a mutable borrow of | |
316 | // a local variable, then just suggest the user remove it. | |
dfeec247 XL |
317 | PlaceRef { local: _, projection: [] } |
318 | if { | |
0bf4aa26 | 319 | if let Ok(snippet) = self.infcx.tcx.sess.source_map().span_to_snippet(span) { |
8faf50e0 XL |
320 | snippet.starts_with("&mut ") |
321 | } else { | |
322 | false | |
323 | } | |
324 | } => | |
325 | { | |
326 | err.span_label(span, format!("cannot {ACT}", ACT = act)); | |
327 | err.span_label(span, "try removing `&mut` here"); | |
328 | } | |
329 | ||
dfeec247 | 330 | PlaceRef { local, projection: [ProjectionElem::Deref] } |
74b04a01 | 331 | if self.body.local_decls[local].is_ref_for_guard() => |
dfeec247 | 332 | { |
8faf50e0 XL |
333 | err.span_label(span, format!("cannot {ACT}", ACT = act)); |
334 | err.note( | |
335 | "variables bound in patterns are immutable until the end of the pattern guard", | |
336 | ); | |
337 | } | |
338 | ||
339 | // We want to point out when a `&` can be readily replaced | |
340 | // with an `&mut`. | |
341 | // | |
342 | // FIXME: can this case be generalized to work for an | |
343 | // arbitrary base for the projection? | |
dfeec247 | 344 | PlaceRef { local, projection: [ProjectionElem::Deref] } |
74b04a01 | 345 | if self.body.local_decls[local].is_user_variable() => |
8faf50e0 | 346 | { |
74b04a01 | 347 | let local_decl = &self.body.local_decls[local]; |
8faf50e0 XL |
348 | |
349 | let (pointer_sigil, pointer_desc) = if local_decl.ty.is_region_ptr() { | |
350 | ("&", "reference") | |
351 | } else { | |
352 | ("*const", "pointer") | |
353 | }; | |
354 | ||
74b04a01 | 355 | match self.local_names[local] { |
dc9dc135 | 356 | Some(name) if !local_decl.from_compiler_desugaring() => { |
f9f354fc XL |
357 | let label = match local_decl.local_info.as_ref().unwrap() { |
358 | box LocalInfo::User(ClearCrossCrate::Set( | |
74b04a01 | 359 | mir::BindingForm::ImplicitSelf(_), |
ba9703b0 XL |
360 | )) => { |
361 | let (span, suggestion) = | |
362 | suggest_ampmut_self(self.infcx.tcx, local_decl); | |
363 | Some((true, span, suggestion)) | |
364 | } | |
74b04a01 | 365 | |
f9f354fc | 366 | box LocalInfo::User(ClearCrossCrate::Set(mir::BindingForm::Var( |
74b04a01 XL |
367 | mir::VarBindingForm { |
368 | binding_mode: ty::BindingMode::BindByValue(_), | |
369 | opt_ty_info, | |
370 | .. | |
371 | }, | |
ba9703b0 XL |
372 | ))) => { |
373 | // check if the RHS is from desugaring | |
374 | let locations = self.body.find_assignments(local); | |
375 | let opt_assignment_rhs_span = locations | |
376 | .first() | |
377 | .map(|&location| self.body.source_info(location).span); | |
378 | let opt_desugaring_kind = | |
379 | opt_assignment_rhs_span.and_then(|span| span.desugaring_kind()); | |
380 | match opt_desugaring_kind { | |
381 | // on for loops, RHS points to the iterator part | |
6a06907d XL |
382 | Some(DesugaringKind::ForLoop(_)) => { |
383 | self.suggest_similar_mut_method_for_for_loop(&mut err); | |
384 | Some(( | |
385 | false, | |
386 | opt_assignment_rhs_span.unwrap(), | |
387 | format!( | |
388 | "this iterator yields `{SIGIL}` {DESC}s", | |
389 | SIGIL = pointer_sigil, | |
390 | DESC = pointer_desc | |
391 | ), | |
392 | )) | |
393 | } | |
ba9703b0 XL |
394 | // don't create labels for compiler-generated spans |
395 | Some(_) => None, | |
396 | None => { | |
397 | let (span, suggestion) = suggest_ampmut( | |
398 | self.infcx.tcx, | |
399 | local_decl, | |
400 | opt_assignment_rhs_span, | |
f9f354fc | 401 | *opt_ty_info, |
ba9703b0 XL |
402 | ); |
403 | Some((true, span, suggestion)) | |
404 | } | |
405 | } | |
406 | } | |
74b04a01 | 407 | |
f9f354fc | 408 | box LocalInfo::User(ClearCrossCrate::Set(mir::BindingForm::Var( |
74b04a01 XL |
409 | mir::VarBindingForm { |
410 | binding_mode: ty::BindingMode::BindByReference(_), | |
411 | .. | |
412 | }, | |
413 | ))) => { | |
414 | let pattern_span = local_decl.source_info.span; | |
415 | suggest_ref_mut(self.infcx.tcx, pattern_span) | |
ba9703b0 | 416 | .map(|replacement| (true, pattern_span, replacement)) |
74b04a01 XL |
417 | } |
418 | ||
f9f354fc | 419 | box LocalInfo::User(ClearCrossCrate::Clear) => { |
74b04a01 XL |
420 | bug!("saw cleared local state") |
421 | } | |
422 | ||
423 | _ => unreachable!(), | |
424 | }; | |
425 | ||
ba9703b0 XL |
426 | match label { |
427 | Some((true, err_help_span, suggested_code)) => { | |
17df50a5 XL |
428 | let (is_trait_sig, local_trait) = self.is_error_in_trait(local); |
429 | if !is_trait_sig { | |
430 | err.span_suggestion( | |
431 | err_help_span, | |
432 | &format!( | |
433 | "consider changing this to be a mutable {}", | |
434 | pointer_desc | |
435 | ), | |
436 | suggested_code, | |
437 | Applicability::MachineApplicable, | |
438 | ); | |
439 | } else if let Some(x) = local_trait { | |
440 | err.span_suggestion( | |
441 | x, | |
442 | &format!( | |
443 | "consider changing that to be a mutable {}", | |
444 | pointer_desc | |
445 | ), | |
446 | suggested_code, | |
447 | Applicability::MachineApplicable, | |
448 | ); | |
449 | } | |
ba9703b0 XL |
450 | } |
451 | Some((false, err_label_span, message)) => { | |
452 | err.span_label(err_label_span, &message); | |
453 | } | |
454 | None => {} | |
74b04a01 | 455 | } |
dc9dc135 XL |
456 | err.span_label( |
457 | span, | |
458 | format!( | |
459 | "`{NAME}` is a `{SIGIL}` {DESC}, \ | |
460 | so the data it refers to cannot be {ACTED_ON}", | |
461 | NAME = name, | |
462 | SIGIL = pointer_sigil, | |
463 | DESC = pointer_desc, | |
464 | ACTED_ON = acted_on | |
465 | ), | |
466 | ); | |
467 | } | |
468 | _ => { | |
469 | err.span_label( | |
470 | span, | |
471 | format!( | |
472 | "cannot {ACT} through `{SIGIL}` {DESC}", | |
473 | ACT = act, | |
474 | SIGIL = pointer_sigil, | |
475 | DESC = pointer_desc | |
476 | ), | |
477 | ); | |
478 | } | |
8faf50e0 XL |
479 | } |
480 | } | |
481 | ||
17df50a5 XL |
482 | PlaceRef { local, projection: [ProjectionElem::Deref] } |
483 | if local == ty::CAPTURE_STRUCT_LOCAL && !self.upvars.is_empty() => | |
484 | { | |
74b04a01 | 485 | self.expected_fn_found_fn_mut_call(&mut err, span, act); |
8faf50e0 XL |
486 | } |
487 | ||
dfeec247 | 488 | PlaceRef { local: _, projection: [.., ProjectionElem::Deref] } => { |
b7449926 XL |
489 | err.span_label(span, format!("cannot {ACT}", ACT = act)); |
490 | ||
416331ca XL |
491 | match opt_source { |
492 | Some(BorrowedContentSource::OverloadedDeref(ty)) => { | |
dfeec247 XL |
493 | err.help(&format!( |
494 | "trait `DerefMut` is required to modify through a dereference, \ | |
416331ca | 495 | but it is not implemented for `{}`", |
dfeec247 XL |
496 | ty, |
497 | )); | |
498 | } | |
416331ca | 499 | Some(BorrowedContentSource::OverloadedIndex(ty)) => { |
dfeec247 XL |
500 | err.help(&format!( |
501 | "trait `IndexMut` is required to modify indexed content, \ | |
416331ca | 502 | but it is not implemented for `{}`", |
dfeec247 XL |
503 | ty, |
504 | )); | |
b7449926 | 505 | } |
416331ca | 506 | _ => (), |
b7449926 XL |
507 | } |
508 | } | |
509 | ||
8faf50e0 XL |
510 | _ => { |
511 | err.span_label(span, format!("cannot {ACT}", ACT = act)); | |
512 | } | |
513 | } | |
514 | ||
515 | err.buffer(&mut self.errors_buffer); | |
516 | } | |
74b04a01 | 517 | |
17df50a5 XL |
518 | /// User cannot make signature of a trait mutable without changing the |
519 | /// trait. So we find if this error belongs to a trait and if so we move | |
520 | /// suggestion to the trait or disable it if it is out of scope of this crate | |
521 | fn is_error_in_trait(&self, local: Local) -> (bool, Option<Span>) { | |
522 | if self.body.local_kind(local) != LocalKind::Arg { | |
523 | return (false, None); | |
524 | } | |
525 | let hir_map = self.infcx.tcx.hir(); | |
526 | let my_def = self.body.source.def_id(); | |
527 | let my_hir = hir_map.local_def_id_to_hir_id(my_def.as_local().unwrap()); | |
528 | let td = if let Some(a) = | |
529 | self.infcx.tcx.impl_of_method(my_def).and_then(|x| self.infcx.tcx.trait_id_of_impl(x)) | |
530 | { | |
531 | a | |
532 | } else { | |
533 | return (false, None); | |
534 | }; | |
535 | ( | |
536 | true, | |
537 | td.as_local().and_then(|tld| { | |
538 | let h = hir_map.local_def_id_to_hir_id(tld); | |
539 | match hir_map.find(h) { | |
540 | Some(Node::Item(hir::Item { | |
541 | kind: hir::ItemKind::Trait(_, _, _, _, items), | |
542 | .. | |
543 | })) => { | |
544 | let mut f_in_trait_opt = None; | |
545 | for hir::TraitItemRef { id: fi, kind: k, .. } in *items { | |
546 | let hi = fi.hir_id(); | |
547 | if !matches!(k, hir::AssocItemKind::Fn { .. }) { | |
548 | continue; | |
549 | } | |
550 | if hir_map.name(hi) != hir_map.name(my_hir) { | |
551 | continue; | |
552 | } | |
553 | f_in_trait_opt = Some(hi); | |
554 | break; | |
555 | } | |
556 | f_in_trait_opt.and_then(|f_in_trait| match hir_map.find(f_in_trait) { | |
557 | Some(Node::TraitItem(hir::TraitItem { | |
558 | kind: | |
559 | hir::TraitItemKind::Fn( | |
560 | hir::FnSig { decl: hir::FnDecl { inputs, .. }, .. }, | |
561 | _, | |
562 | ), | |
563 | .. | |
564 | })) => { | |
565 | let hir::Ty { span, .. } = inputs[local.index() - 1]; | |
566 | Some(span) | |
567 | } | |
568 | _ => None, | |
569 | }) | |
570 | } | |
571 | _ => None, | |
572 | } | |
573 | }), | |
574 | ) | |
575 | } | |
576 | ||
5869c6ff XL |
577 | // point to span of upvar making closure call require mutable borrow |
578 | fn show_mutating_upvar( | |
579 | &self, | |
580 | tcx: TyCtxt<'_>, | |
581 | id: &hir::def_id::DefId, | |
582 | the_place_err: PlaceRef<'tcx>, | |
583 | err: &mut DiagnosticBuilder<'_>, | |
584 | ) { | |
6a06907d XL |
585 | let closure_local_def_id = id.expect_local(); |
586 | let tables = tcx.typeck(closure_local_def_id); | |
587 | let closure_hir_id = tcx.hir().local_def_id_to_hir_id(closure_local_def_id); | |
588 | if let Some((span, closure_kind_origin)) = | |
589 | &tables.closure_kind_origins().get(closure_hir_id) | |
590 | { | |
591 | let reason = if let PlaceBase::Upvar(upvar_id) = closure_kind_origin.base { | |
592 | let upvar = ty::place_to_string_for_capture(tcx, closure_kind_origin); | |
593 | let root_hir_id = upvar_id.var_path.hir_id; | |
594 | // we have a origin for this closure kind starting at this root variable so it's safe to unwrap here | |
595 | let captured_places = tables.closure_min_captures[id].get(&root_hir_id).unwrap(); | |
596 | ||
597 | let origin_projection = closure_kind_origin | |
598 | .projections | |
599 | .iter() | |
600 | .map(|proj| proj.kind) | |
601 | .collect::<Vec<_>>(); | |
602 | let mut capture_reason = String::new(); | |
603 | for captured_place in captured_places { | |
604 | let captured_place_kinds = captured_place | |
605 | .place | |
606 | .projections | |
607 | .iter() | |
608 | .map(|proj| proj.kind) | |
609 | .collect::<Vec<_>>(); | |
610 | if rustc_middle::ty::is_ancestor_or_same_capture( | |
611 | &captured_place_kinds, | |
612 | &origin_projection, | |
613 | ) { | |
614 | match captured_place.info.capture_kind { | |
615 | ty::UpvarCapture::ByRef(ty::UpvarBorrow { | |
616 | kind: ty::BorrowKind::MutBorrow | ty::BorrowKind::UniqueImmBorrow, | |
617 | .. | |
618 | }) => { | |
619 | capture_reason = format!("mutable borrow of `{}`", upvar); | |
620 | } | |
621 | ty::UpvarCapture::ByValue(_) => { | |
622 | capture_reason = format!("possible mutation of `{}`", upvar); | |
623 | } | |
624 | _ => bug!("upvar `{}` borrowed, but not mutably", upvar), | |
625 | } | |
626 | break; | |
627 | } | |
5869c6ff | 628 | } |
6a06907d XL |
629 | if capture_reason.is_empty() { |
630 | bug!("upvar `{}` borrowed, but cannot find reason", upvar); | |
631 | } | |
632 | capture_reason | |
633 | } else { | |
634 | bug!("not an upvar") | |
635 | }; | |
636 | err.span_label( | |
637 | *span, | |
638 | format!( | |
639 | "calling `{}` requires mutable binding due to {}", | |
640 | self.describe_place(the_place_err).unwrap(), | |
641 | reason | |
642 | ), | |
643 | ); | |
644 | } | |
645 | } | |
646 | ||
647 | // Attempt to search similar mutable associated items for suggestion. | |
648 | // In the future, attempt in all path but initially for RHS of for_loop | |
649 | fn suggest_similar_mut_method_for_for_loop(&self, err: &mut DiagnosticBuilder<'_>) { | |
650 | use hir::{ | |
651 | BodyId, Expr, | |
652 | ExprKind::{Block, Call, DropTemps, Match, MethodCall}, | |
653 | HirId, ImplItem, ImplItemKind, Item, ItemKind, | |
654 | }; | |
655 | ||
656 | fn maybe_body_id_of_fn(hir_map: &Map<'tcx>, id: HirId) -> Option<BodyId> { | |
657 | match hir_map.find(id) { | |
658 | Some(Node::Item(Item { kind: ItemKind::Fn(_, _, body_id), .. })) | |
659 | | Some(Node::ImplItem(ImplItem { kind: ImplItemKind::Fn(_, body_id), .. })) => { | |
660 | Some(*body_id) | |
661 | } | |
662 | _ => None, | |
663 | } | |
664 | } | |
665 | let hir_map = self.infcx.tcx.hir(); | |
666 | let mir_body_hir_id = self.mir_hir_id(); | |
667 | if let Some(fn_body_id) = maybe_body_id_of_fn(&hir_map, mir_body_hir_id) { | |
668 | if let Block( | |
669 | hir::Block { | |
670 | expr: | |
671 | Some(Expr { | |
672 | kind: | |
673 | DropTemps(Expr { | |
674 | kind: | |
675 | Match( | |
676 | Expr { | |
677 | kind: | |
678 | Call( | |
679 | _, | |
680 | [Expr { | |
681 | kind: MethodCall(path_segment, ..), | |
682 | hir_id, | |
683 | .. | |
684 | }, ..], | |
685 | ), | |
686 | .. | |
687 | }, | |
688 | .., | |
689 | ), | |
690 | .. | |
691 | }), | |
692 | .. | |
693 | }), | |
694 | .. | |
695 | }, | |
696 | _, | |
697 | ) = hir_map.body(fn_body_id).value.kind | |
698 | { | |
699 | let opt_suggestions = path_segment | |
700 | .hir_id | |
701 | .map(|path_hir_id| self.infcx.tcx.typeck(path_hir_id.owner)) | |
702 | .and_then(|typeck| typeck.type_dependent_def_id(*hir_id)) | |
703 | .and_then(|def_id| self.infcx.tcx.impl_of_method(def_id)) | |
704 | .map(|def_id| self.infcx.tcx.associated_items(def_id)) | |
705 | .map(|assoc_items| { | |
706 | assoc_items | |
707 | .in_definition_order() | |
708 | .map(|assoc_item_def| assoc_item_def.ident) | |
709 | .filter(|&ident| { | |
710 | let original_method_ident = path_segment.ident; | |
711 | original_method_ident != ident | |
712 | && ident | |
713 | .as_str() | |
714 | .starts_with(&original_method_ident.name.to_string()) | |
715 | }) | |
716 | .map(|ident| format!("{}()", ident)) | |
36d6ef2b | 717 | .peekable() |
6a06907d XL |
718 | }); |
719 | ||
36d6ef2b XL |
720 | if let Some(mut suggestions) = opt_suggestions { |
721 | if suggestions.peek().is_some() { | |
722 | err.span_suggestions( | |
723 | path_segment.ident.span, | |
724 | &format!("use mutable method"), | |
725 | suggestions, | |
726 | Applicability::MaybeIncorrect, | |
727 | ); | |
728 | } | |
5869c6ff | 729 | } |
5869c6ff | 730 | } |
5869c6ff | 731 | }; |
5869c6ff XL |
732 | } |
733 | ||
74b04a01 XL |
734 | /// Targeted error when encountering an `FnMut` closure where an `Fn` closure was expected. |
735 | fn expected_fn_found_fn_mut_call(&self, err: &mut DiagnosticBuilder<'_>, sp: Span, act: &str) { | |
736 | err.span_label(sp, format!("cannot {}", act)); | |
737 | ||
738 | let hir = self.infcx.tcx.hir(); | |
29967ef6 | 739 | let closure_id = self.mir_hir_id(); |
74b04a01 XL |
740 | let fn_call_id = hir.get_parent_node(closure_id); |
741 | let node = hir.get(fn_call_id); | |
f035d41b | 742 | let item_id = hir.enclosing_body_owner(fn_call_id); |
74b04a01 XL |
743 | let mut look_at_return = true; |
744 | // If we can detect the expression to be an `fn` call where the closure was an argument, | |
745 | // we point at the `fn` definition argument... | |
ba9703b0 XL |
746 | if let hir::Node::Expr(hir::Expr { kind: hir::ExprKind::Call(func, args), .. }) = node { |
747 | let arg_pos = args | |
748 | .iter() | |
749 | .enumerate() | |
750 | .filter(|(_, arg)| arg.span == self.body.span) | |
751 | .map(|(pos, _)| pos) | |
752 | .next(); | |
753 | let def_id = hir.local_def_id(item_id); | |
3dfed10e | 754 | let tables = self.infcx.tcx.typeck(def_id); |
ba9703b0 | 755 | if let Some(ty::FnDef(def_id, _)) = |
1b1a35ee | 756 | tables.node_type_opt(func.hir_id).as_ref().map(|ty| ty.kind()) |
ba9703b0 XL |
757 | { |
758 | let arg = match hir.get_if_local(*def_id) { | |
759 | Some( | |
760 | hir::Node::Item(hir::Item { | |
761 | ident, kind: hir::ItemKind::Fn(sig, ..), .. | |
762 | }) | |
763 | | hir::Node::TraitItem(hir::TraitItem { | |
74b04a01 | 764 | ident, |
ba9703b0 | 765 | kind: hir::TraitItemKind::Fn(sig, _), |
74b04a01 | 766 | .. |
ba9703b0 XL |
767 | }) |
768 | | hir::Node::ImplItem(hir::ImplItem { | |
74b04a01 | 769 | ident, |
ba9703b0 | 770 | kind: hir::ImplItemKind::Fn(sig, _), |
74b04a01 | 771 | .. |
ba9703b0 XL |
772 | }), |
773 | ) => Some( | |
774 | arg_pos | |
775 | .and_then(|pos| { | |
776 | sig.decl.inputs.get( | |
777 | pos + if sig.decl.implicit_self.has_implicit_self() { | |
778 | 1 | |
779 | } else { | |
780 | 0 | |
781 | }, | |
782 | ) | |
783 | }) | |
784 | .map(|arg| arg.span) | |
785 | .unwrap_or(ident.span), | |
786 | ), | |
787 | _ => None, | |
788 | }; | |
789 | if let Some(span) = arg { | |
790 | err.span_label(span, "change this to accept `FnMut` instead of `Fn`"); | |
791 | err.span_label(func.span, "expects `Fn` instead of `FnMut`"); | |
792 | if self.infcx.tcx.sess.source_map().is_multiline(self.body.span) { | |
793 | err.span_label(self.body.span, "in this closure"); | |
74b04a01 | 794 | } |
ba9703b0 | 795 | look_at_return = false; |
74b04a01 XL |
796 | } |
797 | } | |
74b04a01 | 798 | } |
ba9703b0 | 799 | |
74b04a01 XL |
800 | if look_at_return && hir.get_return_block(closure_id).is_some() { |
801 | // ...otherwise we are probably in the tail expression of the function, point at the | |
802 | // return type. | |
803 | match hir.get(hir.get_parent_item(fn_call_id)) { | |
804 | hir::Node::Item(hir::Item { ident, kind: hir::ItemKind::Fn(sig, ..), .. }) | |
805 | | hir::Node::TraitItem(hir::TraitItem { | |
806 | ident, | |
ba9703b0 | 807 | kind: hir::TraitItemKind::Fn(sig, _), |
74b04a01 XL |
808 | .. |
809 | }) | |
810 | | hir::Node::ImplItem(hir::ImplItem { | |
811 | ident, | |
ba9703b0 | 812 | kind: hir::ImplItemKind::Fn(sig, _), |
74b04a01 XL |
813 | .. |
814 | }) => { | |
815 | err.span_label(ident.span, ""); | |
816 | err.span_label( | |
817 | sig.decl.output.span(), | |
818 | "change this to return `FnMut` instead of `Fn`", | |
819 | ); | |
820 | err.span_label(self.body.span, "in this closure"); | |
821 | } | |
822 | _ => {} | |
823 | } | |
824 | } | |
825 | } | |
b7449926 | 826 | } |
8faf50e0 | 827 | |
29967ef6 XL |
828 | fn mut_borrow_of_mutable_ref(local_decl: &LocalDecl<'_>, local_name: Option<Symbol>) -> bool { |
829 | debug!("local_info: {:?}, ty.kind(): {:?}", local_decl.local_info, local_decl.ty.kind()); | |
830 | ||
831 | match local_decl.local_info.as_deref() { | |
832 | // Check if mutably borrowing a mutable reference. | |
833 | Some(LocalInfo::User(ClearCrossCrate::Set(mir::BindingForm::Var( | |
834 | mir::VarBindingForm { | |
835 | binding_mode: ty::BindingMode::BindByValue(Mutability::Not), .. | |
836 | }, | |
837 | )))) => matches!(local_decl.ty.kind(), ty::Ref(_, _, hir::Mutability::Mut)), | |
838 | Some(LocalInfo::User(ClearCrossCrate::Set(mir::BindingForm::ImplicitSelf(kind)))) => { | |
839 | // Check if the user variable is a `&mut self` and we can therefore | |
840 | // suggest removing the `&mut`. | |
841 | // | |
842 | // Deliberately fall into this case for all implicit self types, | |
843 | // so that we don't fall in to the next case with them. | |
844 | *kind == mir::ImplicitSelfKind::MutRef | |
845 | } | |
846 | _ if Some(kw::SelfLower) == local_name => { | |
847 | // Otherwise, check if the name is the `self` keyword - in which case | |
848 | // we have an explicit self. Do the same thing in this case and check | |
849 | // for a `self: &mut Self` to suggest removing the `&mut`. | |
850 | matches!(local_decl.ty.kind(), ty::Ref(_, _, hir::Mutability::Mut)) | |
851 | } | |
852 | _ => false, | |
853 | } | |
854 | } | |
855 | ||
dc9dc135 XL |
856 | fn suggest_ampmut_self<'tcx>( |
857 | tcx: TyCtxt<'tcx>, | |
b7449926 XL |
858 | local_decl: &mir::LocalDecl<'tcx>, |
859 | ) -> (Span, String) { | |
860 | let sp = local_decl.source_info.span; | |
dfeec247 XL |
861 | ( |
862 | sp, | |
863 | match tcx.sess.source_map().span_to_snippet(sp) { | |
864 | Ok(snippet) => { | |
865 | let lt_pos = snippet.find('\''); | |
866 | if let Some(lt_pos) = lt_pos { | |
867 | format!("&{}mut self", &snippet[lt_pos..snippet.len() - 4]) | |
868 | } else { | |
869 | "&mut self".to_string() | |
870 | } | |
8faf50e0 | 871 | } |
dfeec247 XL |
872 | _ => "&mut self".to_string(), |
873 | }, | |
874 | ) | |
8faf50e0 XL |
875 | } |
876 | ||
877 | // When we want to suggest a user change a local variable to be a `&mut`, there | |
878 | // are three potential "obvious" things to highlight: | |
879 | // | |
880 | // let ident [: Type] [= RightHandSideExpression]; | |
881 | // ^^^^^ ^^^^ ^^^^^^^^^^^^^^^^^^^^^^^ | |
882 | // (1.) (2.) (3.) | |
883 | // | |
884 | // We can always fallback on highlighting the first. But chances are good that | |
885 | // the user experience will be better if we highlight one of the others if possible; | |
886 | // for example, if the RHS is present and the Type is not, then the type is going to | |
887 | // be inferred *from* the RHS, which means we should highlight that (and suggest | |
888 | // that they borrow the RHS mutably). | |
889 | // | |
890 | // This implementation attempts to emulate AST-borrowck prioritization | |
891 | // by trying (3.), then (2.) and finally falling back on (1.). | |
dc9dc135 XL |
892 | fn suggest_ampmut<'tcx>( |
893 | tcx: TyCtxt<'tcx>, | |
8faf50e0 | 894 | local_decl: &mir::LocalDecl<'tcx>, |
ba9703b0 | 895 | opt_assignment_rhs_span: Option<Span>, |
8faf50e0 XL |
896 | opt_ty_info: Option<Span>, |
897 | ) -> (Span, String) { | |
ba9703b0 | 898 | if let Some(assignment_rhs_span) = opt_assignment_rhs_span { |
b7449926 | 899 | if let Ok(src) = tcx.sess.source_map().span_to_snippet(assignment_rhs_span) { |
dfeec247 XL |
900 | if let (true, Some(ws_pos)) = |
901 | (src.starts_with("&'"), src.find(|c: char| -> bool { c.is_whitespace() })) | |
902 | { | |
b7449926 XL |
903 | let lt_name = &src[1..ws_pos]; |
904 | let ty = &src[ws_pos..]; | |
17df50a5 XL |
905 | if !ty.trim_start().starts_with("mut") { |
906 | return (assignment_rhs_span, format!("&{} mut {}", lt_name, ty)); | |
907 | } | |
1b1a35ee | 908 | } else if let Some(stripped) = src.strip_prefix('&') { |
17df50a5 XL |
909 | if !stripped.trim_start().starts_with("mut") { |
910 | return (assignment_rhs_span, format!("&mut {}", stripped)); | |
911 | } | |
8faf50e0 XL |
912 | } |
913 | } | |
914 | } | |
915 | ||
916 | let highlight_span = match opt_ty_info { | |
917 | // if this is a variable binding with an explicit type, | |
918 | // try to highlight that for the suggestion. | |
919 | Some(ty_span) => ty_span, | |
920 | ||
921 | // otherwise, just highlight the span associated with | |
922 | // the (MIR) LocalDecl. | |
923 | None => local_decl.source_info.span, | |
924 | }; | |
925 | ||
b7449926 | 926 | if let Ok(src) = tcx.sess.source_map().span_to_snippet(highlight_span) { |
dfeec247 XL |
927 | if let (true, Some(ws_pos)) = |
928 | (src.starts_with("&'"), src.find(|c: char| -> bool { c.is_whitespace() })) | |
929 | { | |
b7449926 XL |
930 | let lt_name = &src[1..ws_pos]; |
931 | let ty = &src[ws_pos..]; | |
932 | return (highlight_span, format!("&{} mut{}", lt_name, ty)); | |
933 | } | |
934 | } | |
935 | ||
8faf50e0 | 936 | let ty_mut = local_decl.ty.builtin_deref(true).unwrap(); |
dfeec247 XL |
937 | assert_eq!(ty_mut.mutbl, hir::Mutability::Not); |
938 | ( | |
939 | highlight_span, | |
940 | if local_decl.ty.is_region_ptr() { | |
941 | format!("&mut {}", ty_mut.ty) | |
942 | } else { | |
943 | format!("*mut {}", ty_mut.ty) | |
944 | }, | |
945 | ) | |
8faf50e0 XL |
946 | } |
947 | ||
48663c56 | 948 | fn is_closure_or_generator(ty: Ty<'_>) -> bool { |
8faf50e0 XL |
949 | ty.is_closure() || ty.is_generator() |
950 | } | |
0bf4aa26 | 951 | |
9fa01778 | 952 | /// Adds a suggestion to a struct definition given a field access to a local. |
0bf4aa26 XL |
953 | /// This function expects the local to be a reference to a struct in order to produce a suggestion. |
954 | /// | |
955 | /// ```text | |
956 | /// LL | s: &'a String | |
957 | /// | ---------- use `&'a mut String` here to make mutable | |
958 | /// ``` | |
959 | fn annotate_struct_field( | |
dc9dc135 | 960 | tcx: TyCtxt<'tcx>, |
48663c56 | 961 | ty: Ty<'tcx>, |
0bf4aa26 XL |
962 | field: &mir::Field, |
963 | ) -> Option<(Span, String)> { | |
964 | // Expect our local to be a reference to a struct of some kind. | |
1b1a35ee XL |
965 | if let ty::Ref(_, ty, _) = ty.kind() { |
966 | if let ty::Adt(def, _) = ty.kind() { | |
0bf4aa26 XL |
967 | let field = def.all_fields().nth(field.index())?; |
968 | // Use the HIR types to construct the diagnostic message. | |
3dfed10e | 969 | let hir_id = tcx.hir().local_def_id_to_hir_id(field.did.as_local()?); |
dc9dc135 | 970 | let node = tcx.hir().find(hir_id)?; |
0bf4aa26 XL |
971 | // Now we're dealing with the actual struct that we're going to suggest a change to, |
972 | // we can expect a field that is an immutable reference to a type. | |
973 | if let hir::Node::Field(field) = node { | |
dfeec247 XL |
974 | if let hir::TyKind::Rptr( |
975 | lifetime, | |
976 | hir::MutTy { mutbl: hir::Mutability::Not, ref ty }, | |
977 | ) = field.ty.kind | |
978 | { | |
0bf4aa26 XL |
979 | // Get the snippets in two parts - the named lifetime (if there is one) and |
980 | // type being referenced, that way we can reconstruct the snippet without loss | |
981 | // of detail. | |
982 | let type_snippet = tcx.sess.source_map().span_to_snippet(ty.span).ok()?; | |
983 | let lifetime_snippet = if !lifetime.is_elided() { | |
984 | format!("{} ", tcx.sess.source_map().span_to_snippet(lifetime.span).ok()?) | |
985 | } else { | |
986 | String::new() | |
987 | }; | |
988 | ||
989 | return Some(( | |
990 | field.ty.span, | |
dfeec247 | 991 | format!("&{}mut {}", lifetime_snippet, &*type_snippet,), |
0bf4aa26 XL |
992 | )); |
993 | } | |
994 | } | |
995 | } | |
996 | } | |
997 | ||
998 | None | |
999 | } | |
416331ca XL |
1000 | |
1001 | /// If possible, suggest replacing `ref` with `ref mut`. | |
e1599b0c XL |
1002 | fn suggest_ref_mut(tcx: TyCtxt<'_>, binding_span: Span) -> Option<String> { |
1003 | let hi_src = tcx.sess.source_map().span_to_snippet(binding_span).ok()?; | |
dfeec247 | 1004 | if hi_src.starts_with("ref") && hi_src["ref".len()..].starts_with(rustc_lexer::is_whitespace) { |
416331ca XL |
1005 | let replacement = format!("ref mut{}", &hi_src["ref".len()..]); |
1006 | Some(replacement) | |
1007 | } else { | |
1008 | None | |
1009 | } | |
1010 | } |