]>
Commit | Line | Data |
---|---|---|
29967ef6 | 1 | use super::FnCtxt; |
29967ef6 | 2 | |
2b03887a FG |
3 | use crate::errors::{AddReturnTypeSuggestion, ExpectedReturnTypeLabel}; |
4 | use rustc_ast::util::parser::{ExprPrecedence, PREC_POSTFIX}; | |
04454e1e | 5 | use rustc_errors::{Applicability, Diagnostic, MultiSpan}; |
29967ef6 XL |
6 | use rustc_hir as hir; |
7 | use rustc_hir::def::{CtorOf, DefKind}; | |
8 | use rustc_hir::lang_items::LangItem; | |
5099ac24 | 9 | use rustc_hir::{ |
923072b8 | 10 | Expr, ExprKind, GenericBound, Node, Path, QPath, Stmt, StmtKind, TyKind, WherePredicate, |
5099ac24 | 11 | }; |
2b03887a | 12 | use rustc_hir_analysis::astconv::AstConv; |
487cf647 | 13 | use rustc_infer::infer; |
064997fb | 14 | use rustc_infer::traits::{self, StatementAsExpression}; |
6a06907d | 15 | use rustc_middle::lint::in_external_macro; |
487cf647 | 16 | use rustc_middle::ty::{self, Binder, DefIdTree, IsSuggestable, ToPredicate, Ty}; |
2b03887a | 17 | use rustc_session::errors::ExprParenthesesNeeded; |
923072b8 | 18 | use rustc_span::symbol::sym; |
04454e1e | 19 | use rustc_span::Span; |
f2b60f7d | 20 | use rustc_trait_selection::infer::InferCtxtExt; |
2b03887a | 21 | use rustc_trait_selection::traits::error_reporting::DefIdOrName; |
064997fb | 22 | use rustc_trait_selection::traits::query::evaluate_obligation::InferCtxtExt as _; |
487cf647 | 23 | use rustc_trait_selection::traits::NormalizeExt; |
29967ef6 XL |
24 | |
25 | impl<'a, 'tcx> FnCtxt<'a, 'tcx> { | |
487cf647 FG |
26 | pub(crate) fn body_fn_sig(&self) -> Option<ty::FnSig<'tcx>> { |
27 | self.typeck_results | |
28 | .borrow() | |
29 | .liberated_fn_sigs() | |
30 | .get(self.tcx.hir().get_parent_node(self.body_id)) | |
31 | .copied() | |
32 | } | |
33 | ||
5e7ed085 | 34 | pub(in super::super) fn suggest_semicolon_at_end(&self, span: Span, err: &mut Diagnostic) { |
487cf647 FG |
35 | // This suggestion is incorrect for |
36 | // fn foo() -> bool { match () { () => true } || match () { () => true } } | |
29967ef6 XL |
37 | err.span_suggestion_short( |
38 | span.shrink_to_hi(), | |
39 | "consider using a semicolon here", | |
923072b8 | 40 | ";", |
487cf647 | 41 | Applicability::MaybeIncorrect, |
29967ef6 XL |
42 | ); |
43 | } | |
44 | ||
45 | /// On implicit return expressions with mismatched types, provides the following suggestions: | |
46 | /// | |
47 | /// - Points out the method's return type as the reason for the expected type. | |
48 | /// - Possible missing semicolon. | |
49 | /// - Possible missing return type if the return type is the default, and not `fn main()`. | |
50 | pub fn suggest_mismatched_types_on_tail( | |
51 | &self, | |
5e7ed085 | 52 | err: &mut Diagnostic, |
29967ef6 XL |
53 | expr: &'tcx hir::Expr<'tcx>, |
54 | expected: Ty<'tcx>, | |
55 | found: Ty<'tcx>, | |
29967ef6 XL |
56 | blk_id: hir::HirId, |
57 | ) -> bool { | |
58 | let expr = expr.peel_drop_temps(); | |
923072b8 | 59 | self.suggest_missing_semicolon(err, expr, expected, false); |
29967ef6 XL |
60 | let mut pointing_at_return_type = false; |
61 | if let Some((fn_decl, can_suggest)) = self.get_fn_decl(blk_id) { | |
cdc7bbd5 | 62 | let fn_id = self.tcx.hir().get_return_block(blk_id).unwrap(); |
136023e0 XL |
63 | pointing_at_return_type = self.suggest_missing_return_type( |
64 | err, | |
65 | &fn_decl, | |
66 | expected, | |
67 | found, | |
68 | can_suggest, | |
69 | fn_id, | |
70 | ); | |
cdc7bbd5 XL |
71 | self.suggest_missing_break_or_return_expr( |
72 | err, expr, &fn_decl, expected, found, blk_id, fn_id, | |
73 | ); | |
29967ef6 XL |
74 | } |
75 | pointing_at_return_type | |
76 | } | |
77 | ||
f2b60f7d | 78 | /// When encountering an fn-like type, try accessing the output of the type |
2b03887a | 79 | /// and suggesting calling it if it satisfies a predicate (i.e. if the |
f2b60f7d | 80 | /// output has a method or a field): |
04454e1e | 81 | /// ```compile_fail,E0308 |
29967ef6 XL |
82 | /// fn foo(x: usize) -> usize { x } |
83 | /// let x: usize = foo; // suggest calling the `foo` function: `foo(42)` | |
84 | /// ``` | |
f2b60f7d | 85 | pub(crate) fn suggest_fn_call( |
29967ef6 | 86 | &self, |
5e7ed085 | 87 | err: &mut Diagnostic, |
29967ef6 | 88 | expr: &hir::Expr<'_>, |
29967ef6 | 89 | found: Ty<'tcx>, |
f2b60f7d | 90 | can_satisfy: impl FnOnce(Ty<'tcx>) -> bool, |
29967ef6 | 91 | ) -> bool { |
f2b60f7d FG |
92 | let Some((def_id_or_name, output, inputs)) = self.extract_callable_info(expr, found) |
93 | else { return false; }; | |
94 | if can_satisfy(output) { | |
95 | let (sugg_call, mut applicability) = match inputs.len() { | |
923072b8 FG |
96 | 0 => ("".to_string(), Applicability::MachineApplicable), |
97 | 1..=4 => ( | |
f2b60f7d FG |
98 | inputs |
99 | .iter() | |
100 | .map(|ty| { | |
101 | if ty.is_suggestable(self.tcx, false) { | |
102 | format!("/* {ty} */") | |
103 | } else { | |
2b03887a | 104 | "/* value */".to_string() |
f2b60f7d FG |
105 | } |
106 | }) | |
107 | .collect::<Vec<_>>() | |
108 | .join(", "), | |
109 | Applicability::HasPlaceholders, | |
923072b8 | 110 | ), |
f2b60f7d | 111 | _ => ("/* ... */".to_string(), Applicability::HasPlaceholders), |
29967ef6 | 112 | }; |
923072b8 | 113 | |
f2b60f7d FG |
114 | let msg = match def_id_or_name { |
115 | DefIdOrName::DefId(def_id) => match self.tcx.def_kind(def_id) { | |
2b03887a FG |
116 | DefKind::Ctor(CtorOf::Struct, _) => "construct this tuple struct".to_string(), |
117 | DefKind::Ctor(CtorOf::Variant, _) => "construct this tuple variant".to_string(), | |
f2b60f7d FG |
118 | kind => format!("call this {}", kind.descr(def_id)), |
119 | }, | |
120 | DefIdOrName::Name(name) => format!("call this {name}"), | |
923072b8 FG |
121 | }; |
122 | ||
123 | let sugg = match expr.kind { | |
124 | hir::ExprKind::Call(..) | |
125 | | hir::ExprKind::Path(..) | |
126 | | hir::ExprKind::Index(..) | |
127 | | hir::ExprKind::Lit(..) => { | |
128 | vec![(expr.span.shrink_to_hi(), format!("({sugg_call})"))] | |
29967ef6 | 129 | } |
923072b8 FG |
130 | hir::ExprKind::Closure { .. } => { |
131 | // Might be `{ expr } || { bool }` | |
132 | applicability = Applicability::MaybeIncorrect; | |
133 | vec![ | |
134 | (expr.span.shrink_to_lo(), "(".to_string()), | |
135 | (expr.span.shrink_to_hi(), format!(")({sugg_call})")), | |
136 | ] | |
29967ef6 | 137 | } |
923072b8 FG |
138 | _ => { |
139 | vec![ | |
140 | (expr.span.shrink_to_lo(), "(".to_string()), | |
141 | (expr.span.shrink_to_hi(), format!(")({sugg_call})")), | |
142 | ] | |
29967ef6 | 143 | } |
923072b8 FG |
144 | }; |
145 | ||
146 | err.multipart_suggestion_verbose( | |
147 | format!("use parentheses to {msg}"), | |
148 | sugg, | |
29967ef6 XL |
149 | applicability, |
150 | ); | |
151 | return true; | |
152 | } | |
153 | false | |
154 | } | |
155 | ||
f2b60f7d FG |
156 | /// Extracts information about a callable type for diagnostics. This is a |
157 | /// heuristic -- it doesn't necessarily mean that a type is always callable, | |
158 | /// because the callable type must also be well-formed to be called. | |
159 | pub(in super::super) fn extract_callable_info( | |
160 | &self, | |
161 | expr: &Expr<'_>, | |
162 | found: Ty<'tcx>, | |
163 | ) -> Option<(DefIdOrName, Ty<'tcx>, Vec<Ty<'tcx>>)> { | |
164 | // Autoderef is useful here because sometimes we box callables, etc. | |
165 | let Some((def_id_or_name, output, inputs)) = self.autoderef(expr.span, found).silence_errors().find_map(|(found, _)| { | |
166 | match *found.kind() { | |
167 | ty::FnPtr(fn_sig) => | |
168 | Some((DefIdOrName::Name("function pointer"), fn_sig.output(), fn_sig.inputs())), | |
169 | ty::FnDef(def_id, _) => { | |
170 | let fn_sig = found.fn_sig(self.tcx); | |
171 | Some((DefIdOrName::DefId(def_id), fn_sig.output(), fn_sig.inputs())) | |
172 | } | |
173 | ty::Closure(def_id, substs) => { | |
174 | let fn_sig = substs.as_closure().sig(); | |
175 | Some((DefIdOrName::DefId(def_id), fn_sig.output(), fn_sig.inputs().map_bound(|inputs| &inputs[1..]))) | |
176 | } | |
177 | ty::Opaque(def_id, substs) => { | |
178 | self.tcx.bound_item_bounds(def_id).subst(self.tcx, substs).iter().find_map(|pred| { | |
487cf647 | 179 | if let ty::PredicateKind::Clause(ty::Clause::Projection(proj)) = pred.kind().skip_binder() |
f2b60f7d FG |
180 | && Some(proj.projection_ty.item_def_id) == self.tcx.lang_items().fn_once_output() |
181 | // args tuple will always be substs[1] | |
182 | && let ty::Tuple(args) = proj.projection_ty.substs.type_at(1).kind() | |
183 | { | |
184 | Some(( | |
185 | DefIdOrName::DefId(def_id), | |
186 | pred.kind().rebind(proj.term.ty().unwrap()), | |
187 | pred.kind().rebind(args.as_slice()), | |
188 | )) | |
189 | } else { | |
190 | None | |
191 | } | |
192 | }) | |
193 | } | |
194 | ty::Dynamic(data, _, ty::Dyn) => { | |
195 | data.iter().find_map(|pred| { | |
196 | if let ty::ExistentialPredicate::Projection(proj) = pred.skip_binder() | |
197 | && Some(proj.item_def_id) == self.tcx.lang_items().fn_once_output() | |
198 | // for existential projection, substs are shifted over by 1 | |
199 | && let ty::Tuple(args) = proj.substs.type_at(0).kind() | |
200 | { | |
201 | Some(( | |
202 | DefIdOrName::Name("trait object"), | |
203 | pred.rebind(proj.term.ty().unwrap()), | |
204 | pred.rebind(args.as_slice()), | |
205 | )) | |
206 | } else { | |
207 | None | |
208 | } | |
209 | }) | |
210 | } | |
211 | ty::Param(param) => { | |
212 | let def_id = self.tcx.generics_of(self.body_id.owner).type_param(¶m, self.tcx).def_id; | |
213 | self.tcx.predicates_of(self.body_id.owner).predicates.iter().find_map(|(pred, _)| { | |
487cf647 | 214 | if let ty::PredicateKind::Clause(ty::Clause::Projection(proj)) = pred.kind().skip_binder() |
f2b60f7d FG |
215 | && Some(proj.projection_ty.item_def_id) == self.tcx.lang_items().fn_once_output() |
216 | && proj.projection_ty.self_ty() == found | |
217 | // args tuple will always be substs[1] | |
218 | && let ty::Tuple(args) = proj.projection_ty.substs.type_at(1).kind() | |
219 | { | |
220 | Some(( | |
221 | DefIdOrName::DefId(def_id), | |
222 | pred.kind().rebind(proj.term.ty().unwrap()), | |
223 | pred.kind().rebind(args.as_slice()), | |
224 | )) | |
225 | } else { | |
226 | None | |
227 | } | |
228 | }) | |
229 | } | |
230 | _ => None, | |
231 | } | |
232 | }) else { return None; }; | |
233 | ||
234 | let output = self.replace_bound_vars_with_fresh_vars(expr.span, infer::FnCall, output); | |
235 | let inputs = inputs | |
236 | .skip_binder() | |
237 | .iter() | |
238 | .map(|ty| { | |
239 | self.replace_bound_vars_with_fresh_vars( | |
240 | expr.span, | |
241 | infer::FnCall, | |
242 | inputs.rebind(*ty), | |
243 | ) | |
244 | }) | |
245 | .collect(); | |
246 | ||
247 | // We don't want to register any extra obligations, which should be | |
248 | // implied by wf, but also because that would possibly result in | |
249 | // erroneous errors later on. | |
250 | let infer::InferOk { value: output, obligations: _ } = | |
487cf647 | 251 | self.at(&self.misc(expr.span), self.param_env).normalize(output); |
f2b60f7d FG |
252 | |
253 | if output.is_ty_var() { None } else { Some((def_id_or_name, output, inputs)) } | |
254 | } | |
255 | ||
256 | pub fn suggest_two_fn_call( | |
257 | &self, | |
258 | err: &mut Diagnostic, | |
259 | lhs_expr: &'tcx hir::Expr<'tcx>, | |
260 | lhs_ty: Ty<'tcx>, | |
261 | rhs_expr: &'tcx hir::Expr<'tcx>, | |
262 | rhs_ty: Ty<'tcx>, | |
263 | can_satisfy: impl FnOnce(Ty<'tcx>, Ty<'tcx>) -> bool, | |
264 | ) -> bool { | |
265 | let Some((_, lhs_output_ty, lhs_inputs)) = self.extract_callable_info(lhs_expr, lhs_ty) | |
266 | else { return false; }; | |
267 | let Some((_, rhs_output_ty, rhs_inputs)) = self.extract_callable_info(rhs_expr, rhs_ty) | |
268 | else { return false; }; | |
269 | ||
270 | if can_satisfy(lhs_output_ty, rhs_output_ty) { | |
271 | let mut sugg = vec![]; | |
272 | let mut applicability = Applicability::MachineApplicable; | |
273 | ||
274 | for (expr, inputs) in [(lhs_expr, lhs_inputs), (rhs_expr, rhs_inputs)] { | |
275 | let (sugg_call, this_applicability) = match inputs.len() { | |
276 | 0 => ("".to_string(), Applicability::MachineApplicable), | |
277 | 1..=4 => ( | |
278 | inputs | |
279 | .iter() | |
280 | .map(|ty| { | |
281 | if ty.is_suggestable(self.tcx, false) { | |
282 | format!("/* {ty} */") | |
283 | } else { | |
284 | "/* value */".to_string() | |
285 | } | |
286 | }) | |
287 | .collect::<Vec<_>>() | |
288 | .join(", "), | |
289 | Applicability::HasPlaceholders, | |
290 | ), | |
291 | _ => ("/* ... */".to_string(), Applicability::HasPlaceholders), | |
292 | }; | |
293 | ||
294 | applicability = applicability.max(this_applicability); | |
295 | ||
296 | match expr.kind { | |
297 | hir::ExprKind::Call(..) | |
298 | | hir::ExprKind::Path(..) | |
299 | | hir::ExprKind::Index(..) | |
300 | | hir::ExprKind::Lit(..) => { | |
301 | sugg.extend([(expr.span.shrink_to_hi(), format!("({sugg_call})"))]); | |
302 | } | |
303 | hir::ExprKind::Closure { .. } => { | |
304 | // Might be `{ expr } || { bool }` | |
305 | applicability = Applicability::MaybeIncorrect; | |
306 | sugg.extend([ | |
307 | (expr.span.shrink_to_lo(), "(".to_string()), | |
308 | (expr.span.shrink_to_hi(), format!(")({sugg_call})")), | |
309 | ]); | |
310 | } | |
311 | _ => { | |
312 | sugg.extend([ | |
313 | (expr.span.shrink_to_lo(), "(".to_string()), | |
314 | (expr.span.shrink_to_hi(), format!(")({sugg_call})")), | |
315 | ]); | |
316 | } | |
317 | } | |
318 | } | |
319 | ||
320 | err.multipart_suggestion_verbose( | |
321 | format!("use parentheses to call these"), | |
322 | sugg, | |
323 | applicability, | |
324 | ); | |
325 | ||
326 | true | |
327 | } else { | |
328 | false | |
329 | } | |
330 | } | |
331 | ||
29967ef6 XL |
332 | pub fn suggest_deref_ref_or_into( |
333 | &self, | |
5e7ed085 | 334 | err: &mut Diagnostic, |
5099ac24 | 335 | expr: &hir::Expr<'tcx>, |
29967ef6 XL |
336 | expected: Ty<'tcx>, |
337 | found: Ty<'tcx>, | |
338 | expected_ty_expr: Option<&'tcx hir::Expr<'tcx>>, | |
2b03887a | 339 | ) -> bool { |
cdc7bbd5 | 340 | let expr = expr.peel_blocks(); |
2b03887a | 341 | if let Some((sp, msg, suggestion, applicability, verbose, annotation)) = |
94222f64 XL |
342 | self.check_ref(expr, found, expected) |
343 | { | |
344 | if verbose { | |
5e7ed085 | 345 | err.span_suggestion_verbose(sp, &msg, suggestion, applicability); |
94222f64 | 346 | } else { |
5e7ed085 | 347 | err.span_suggestion(sp, &msg, suggestion, applicability); |
94222f64 | 348 | } |
2b03887a FG |
349 | if annotation { |
350 | let suggest_annotation = match expr.peel_drop_temps().kind { | |
487cf647 | 351 | hir::ExprKind::AddrOf(hir::BorrowKind::Ref, mutbl, _) => mutbl.ref_prefix_str(), |
2b03887a FG |
352 | _ => return true, |
353 | }; | |
354 | let mut tuple_indexes = Vec::new(); | |
355 | let mut expr_id = expr.hir_id; | |
356 | for (parent_id, node) in self.tcx.hir().parent_iter(expr.hir_id) { | |
357 | match node { | |
358 | Node::Expr(&Expr { kind: ExprKind::Tup(subs), .. }) => { | |
359 | tuple_indexes.push( | |
360 | subs.iter() | |
361 | .enumerate() | |
362 | .find(|(_, sub_expr)| sub_expr.hir_id == expr_id) | |
363 | .unwrap() | |
364 | .0, | |
365 | ); | |
366 | expr_id = parent_id; | |
367 | } | |
368 | Node::Local(local) => { | |
369 | if let Some(mut ty) = local.ty { | |
370 | while let Some(index) = tuple_indexes.pop() { | |
371 | match ty.kind { | |
372 | TyKind::Tup(tys) => ty = &tys[index], | |
373 | _ => return true, | |
374 | } | |
375 | } | |
376 | let annotation_span = ty.span; | |
377 | err.span_suggestion( | |
378 | annotation_span.with_hi(annotation_span.lo()), | |
487cf647 | 379 | "alternatively, consider changing the type annotation", |
2b03887a FG |
380 | suggest_annotation, |
381 | Applicability::MaybeIncorrect, | |
382 | ); | |
383 | } | |
384 | break; | |
385 | } | |
386 | _ => break, | |
387 | } | |
388 | } | |
389 | } | |
390 | return true; | |
391 | } else if self.suggest_else_fn_with_closure(err, expr, found, expected) { | |
392 | return true; | |
f2b60f7d FG |
393 | } else if self.suggest_fn_call(err, expr, found, |output| self.can_coerce(output, expected)) |
394 | && let ty::FnDef(def_id, ..) = &found.kind() | |
395 | && let Some(sp) = self.tcx.hir().span_if_local(*def_id) | |
29967ef6 | 396 | { |
f2b60f7d | 397 | err.span_label(sp, format!("{found} defined here")); |
2b03887a FG |
398 | return true; |
399 | } else if self.check_for_cast(err, expr, found, expected, expected_ty_expr) { | |
400 | return true; | |
401 | } else { | |
29967ef6 | 402 | let methods = self.get_conversion_methods(expr.span, expected, found, expr.hir_id); |
3c0e092e | 403 | if !methods.is_empty() { |
064997fb FG |
404 | let mut suggestions = methods.iter() |
405 | .filter_map(|conversion_method| { | |
406 | let receiver_method_ident = expr.method_ident(); | |
407 | if let Some(method_ident) = receiver_method_ident | |
408 | && method_ident.name == conversion_method.name | |
409 | { | |
410 | return None // do not suggest code that is already there (#53348) | |
411 | } | |
412 | ||
413 | let method_call_list = [sym::to_vec, sym::to_string]; | |
414 | let mut sugg = if let ExprKind::MethodCall(receiver_method, ..) = expr.kind | |
415 | && receiver_method.ident.name == sym::clone | |
416 | && method_call_list.contains(&conversion_method.name) | |
417 | // If receiver is `.clone()` and found type has one of those methods, | |
418 | // we guess that the user wants to convert from a slice type (`&[]` or `&str`) | |
419 | // to an owned type (`Vec` or `String`). These conversions clone internally, | |
420 | // so we remove the user's `clone` call. | |
421 | { | |
422 | vec![( | |
423 | receiver_method.ident.span, | |
424 | conversion_method.name.to_string() | |
425 | )] | |
426 | } else if expr.precedence().order() | |
427 | < ExprPrecedence::MethodCall.order() | |
428 | { | |
429 | vec![ | |
430 | (expr.span.shrink_to_lo(), "(".to_string()), | |
431 | (expr.span.shrink_to_hi(), format!(").{}()", conversion_method.name)), | |
432 | ] | |
433 | } else { | |
434 | vec![(expr.span.shrink_to_hi(), format!(".{}()", conversion_method.name))] | |
435 | }; | |
436 | let struct_pat_shorthand_field = self.maybe_get_struct_pattern_shorthand_field(expr); | |
437 | if let Some(name) = struct_pat_shorthand_field { | |
438 | sugg.insert( | |
439 | 0, | |
440 | (expr.span.shrink_to_lo(), format!("{}: ", name)), | |
441 | ); | |
442 | } | |
443 | Some(sugg) | |
444 | }) | |
445 | .peekable(); | |
446 | if suggestions.peek().is_some() { | |
447 | err.multipart_suggestions( | |
448 | "try using a conversion method", | |
449 | suggestions, | |
450 | Applicability::MaybeIncorrect, | |
451 | ); | |
2b03887a | 452 | return true; |
3c0e092e | 453 | } |
064997fb FG |
454 | } else if let ty::Adt(found_adt, found_substs) = found.kind() |
455 | && self.tcx.is_diagnostic_item(sym::Option, found_adt.did()) | |
456 | && let ty::Adt(expected_adt, expected_substs) = expected.kind() | |
457 | && self.tcx.is_diagnostic_item(sym::Option, expected_adt.did()) | |
458 | && let ty::Ref(_, inner_ty, _) = expected_substs.type_at(0).kind() | |
459 | && inner_ty.is_str() | |
3c0e092e | 460 | { |
064997fb FG |
461 | let ty = found_substs.type_at(0); |
462 | let mut peeled = ty; | |
463 | let mut ref_cnt = 0; | |
464 | while let ty::Ref(_, inner, _) = peeled.kind() { | |
465 | peeled = *inner; | |
466 | ref_cnt += 1; | |
467 | } | |
468 | if let ty::Adt(adt, _) = peeled.kind() | |
487cf647 | 469 | && Some(adt.did()) == self.tcx.lang_items().string() |
064997fb FG |
470 | { |
471 | err.span_suggestion_verbose( | |
472 | expr.span.shrink_to_hi(), | |
473 | "try converting the passed type into a `&str`", | |
474 | format!(".map(|x| &*{}x)", "*".repeat(ref_cnt)), | |
475 | Applicability::MaybeIncorrect, | |
476 | ); | |
2b03887a | 477 | return true; |
29967ef6 XL |
478 | } |
479 | } | |
480 | } | |
2b03887a FG |
481 | |
482 | false | |
29967ef6 XL |
483 | } |
484 | ||
485 | /// When encountering the expected boxed value allocated in the stack, suggest allocating it | |
486 | /// in the heap by calling `Box::new()`. | |
487 | pub(in super::super) fn suggest_boxing_when_appropriate( | |
488 | &self, | |
5e7ed085 | 489 | err: &mut Diagnostic, |
29967ef6 XL |
490 | expr: &hir::Expr<'_>, |
491 | expected: Ty<'tcx>, | |
492 | found: Ty<'tcx>, | |
2b03887a | 493 | ) -> bool { |
29967ef6 XL |
494 | if self.tcx.hir().is_inside_const_context(expr.hir_id) { |
495 | // Do not suggest `Box::new` in const context. | |
2b03887a | 496 | return false; |
29967ef6 XL |
497 | } |
498 | if !expected.is_box() || found.is_box() { | |
2b03887a | 499 | return false; |
29967ef6 XL |
500 | } |
501 | let boxed_found = self.tcx.mk_box(found); | |
94222f64 XL |
502 | if self.can_coerce(boxed_found, expected) { |
503 | err.multipart_suggestion( | |
29967ef6 | 504 | "store this in the heap by calling `Box::new`", |
94222f64 XL |
505 | vec![ |
506 | (expr.span.shrink_to_lo(), "Box::new(".to_string()), | |
507 | (expr.span.shrink_to_hi(), ")".to_string()), | |
508 | ], | |
29967ef6 XL |
509 | Applicability::MachineApplicable, |
510 | ); | |
511 | err.note( | |
512 | "for more on the distinction between the stack and the heap, read \ | |
513 | https://doc.rust-lang.org/book/ch15-01-box.html, \ | |
514 | https://doc.rust-lang.org/rust-by-example/std/box.html, and \ | |
515 | https://doc.rust-lang.org/std/boxed/index.html", | |
516 | ); | |
2b03887a FG |
517 | true |
518 | } else { | |
519 | false | |
29967ef6 XL |
520 | } |
521 | } | |
522 | ||
5869c6ff XL |
523 | /// When encountering a closure that captures variables, where a FnPtr is expected, |
524 | /// suggest a non-capturing closure | |
525 | pub(in super::super) fn suggest_no_capture_closure( | |
526 | &self, | |
5e7ed085 | 527 | err: &mut Diagnostic, |
5869c6ff XL |
528 | expected: Ty<'tcx>, |
529 | found: Ty<'tcx>, | |
2b03887a | 530 | ) -> bool { |
5869c6ff XL |
531 | if let (ty::FnPtr(_), ty::Closure(def_id, _)) = (expected.kind(), found.kind()) { |
532 | if let Some(upvars) = self.tcx.upvars_mentioned(*def_id) { | |
533 | // Report upto four upvars being captured to reduce the amount error messages | |
534 | // reported back to the user. | |
535 | let spans_and_labels = upvars | |
536 | .iter() | |
537 | .take(4) | |
538 | .map(|(var_hir_id, upvar)| { | |
539 | let var_name = self.tcx.hir().name(*var_hir_id).to_string(); | |
540 | let msg = format!("`{}` captured here", var_name); | |
541 | (upvar.span, msg) | |
542 | }) | |
543 | .collect::<Vec<_>>(); | |
544 | ||
545 | let mut multi_span: MultiSpan = | |
546 | spans_and_labels.iter().map(|(sp, _)| *sp).collect::<Vec<_>>().into(); | |
547 | for (sp, label) in spans_and_labels { | |
548 | multi_span.push_span_label(sp, label); | |
549 | } | |
c295e0f8 XL |
550 | err.span_note( |
551 | multi_span, | |
552 | "closures can only be coerced to `fn` types if they do not capture any variables" | |
553 | ); | |
2b03887a | 554 | return true; |
5869c6ff XL |
555 | } |
556 | } | |
2b03887a | 557 | false |
5869c6ff XL |
558 | } |
559 | ||
29967ef6 | 560 | /// When encountering an `impl Future` where `BoxFuture` is expected, suggest `Box::pin`. |
c295e0f8 | 561 | #[instrument(skip(self, err))] |
29967ef6 XL |
562 | pub(in super::super) fn suggest_calling_boxed_future_when_appropriate( |
563 | &self, | |
5e7ed085 | 564 | err: &mut Diagnostic, |
29967ef6 XL |
565 | expr: &hir::Expr<'_>, |
566 | expected: Ty<'tcx>, | |
567 | found: Ty<'tcx>, | |
568 | ) -> bool { | |
569 | // Handle #68197. | |
570 | ||
571 | if self.tcx.hir().is_inside_const_context(expr.hir_id) { | |
572 | // Do not suggest `Box::new` in const context. | |
573 | return false; | |
574 | } | |
575 | let pin_did = self.tcx.lang_items().pin_type(); | |
c295e0f8 XL |
576 | // This guards the `unwrap` and `mk_box` below. |
577 | if pin_did.is_none() || self.tcx.lang_items().owned_box().is_none() { | |
578 | return false; | |
29967ef6 | 579 | } |
c295e0f8 XL |
580 | let box_found = self.tcx.mk_box(found); |
581 | let pin_box_found = self.tcx.mk_lang_item(box_found, LangItem::Pin).unwrap(); | |
582 | let pin_found = self.tcx.mk_lang_item(found, LangItem::Pin).unwrap(); | |
583 | match expected.kind() { | |
5e7ed085 | 584 | ty::Adt(def, _) if Some(def.did()) == pin_did => { |
c295e0f8 XL |
585 | if self.can_coerce(pin_box_found, expected) { |
586 | debug!("can coerce {:?} to {:?}, suggesting Box::pin", pin_box_found, expected); | |
587 | match found.kind() { | |
588 | ty::Adt(def, _) if def.is_box() => { | |
589 | err.help("use `Box::pin`"); | |
590 | } | |
591 | _ => { | |
592 | err.multipart_suggestion( | |
593 | "you need to pin and box this expression", | |
594 | vec![ | |
595 | (expr.span.shrink_to_lo(), "Box::pin(".to_string()), | |
596 | (expr.span.shrink_to_hi(), ")".to_string()), | |
597 | ], | |
598 | Applicability::MaybeIncorrect, | |
599 | ); | |
600 | } | |
601 | } | |
602 | true | |
603 | } else if self.can_coerce(pin_found, expected) { | |
604 | match found.kind() { | |
605 | ty::Adt(def, _) if def.is_box() => { | |
606 | err.help("use `Box::pin`"); | |
607 | true | |
608 | } | |
609 | _ => false, | |
610 | } | |
611 | } else { | |
612 | false | |
29967ef6 | 613 | } |
c295e0f8 XL |
614 | } |
615 | ty::Adt(def, _) if def.is_box() && self.can_coerce(box_found, expected) => { | |
616 | // Check if the parent expression is a call to Pin::new. If it | |
617 | // is and we were expecting a Box, ergo Pin<Box<expected>>, we | |
618 | // can suggest Box::pin. | |
619 | let parent = self.tcx.hir().get_parent_node(expr.hir_id); | |
5e7ed085 FG |
620 | let Some(Node::Expr(Expr { kind: ExprKind::Call(fn_name, _), .. })) = self.tcx.hir().find(parent) else { |
621 | return false; | |
c295e0f8 XL |
622 | }; |
623 | match fn_name.kind { | |
624 | ExprKind::Path(QPath::TypeRelative( | |
625 | hir::Ty { | |
626 | kind: TyKind::Path(QPath::Resolved(_, Path { res: recv_ty, .. })), | |
627 | .. | |
628 | }, | |
629 | method, | |
630 | )) if recv_ty.opt_def_id() == pin_did && method.ident.name == sym::new => { | |
631 | err.span_suggestion( | |
632 | fn_name.span, | |
633 | "use `Box::pin` to pin and box this expression", | |
923072b8 | 634 | "Box::pin", |
c295e0f8 XL |
635 | Applicability::MachineApplicable, |
636 | ); | |
637 | true | |
638 | } | |
639 | _ => false, | |
29967ef6 XL |
640 | } |
641 | } | |
c295e0f8 | 642 | _ => false, |
29967ef6 XL |
643 | } |
644 | } | |
645 | ||
646 | /// A common error is to forget to add a semicolon at the end of a block, e.g., | |
647 | /// | |
04454e1e FG |
648 | /// ```compile_fail,E0308 |
649 | /// # fn bar_that_returns_u32() -> u32 { 4 } | |
29967ef6 XL |
650 | /// fn foo() { |
651 | /// bar_that_returns_u32() | |
652 | /// } | |
653 | /// ``` | |
654 | /// | |
655 | /// This routine checks if the return expression in a block would make sense on its own as a | |
656 | /// statement and the return type has been left as default or has been specified as `()`. If so, | |
657 | /// it suggests adding a semicolon. | |
923072b8 FG |
658 | /// |
659 | /// If the expression is the expression of a closure without block (`|| expr`), a | |
660 | /// block is needed to be added too (`|| { expr; }`). This is denoted by `needs_block`. | |
661 | pub fn suggest_missing_semicolon( | |
29967ef6 | 662 | &self, |
5e7ed085 | 663 | err: &mut Diagnostic, |
29967ef6 XL |
664 | expression: &'tcx hir::Expr<'tcx>, |
665 | expected: Ty<'tcx>, | |
923072b8 | 666 | needs_block: bool, |
29967ef6 XL |
667 | ) { |
668 | if expected.is_unit() { | |
669 | // `BlockTailExpression` only relevant if the tail expr would be | |
670 | // useful on its own. | |
671 | match expression.kind { | |
672 | ExprKind::Call(..) | |
673 | | ExprKind::MethodCall(..) | |
674 | | ExprKind::Loop(..) | |
5869c6ff | 675 | | ExprKind::If(..) |
29967ef6 | 676 | | ExprKind::Match(..) |
6a06907d | 677 | | ExprKind::Block(..) |
923072b8 FG |
678 | if expression.can_have_side_effects() |
679 | // If the expression is from an external macro, then do not suggest | |
680 | // adding a semicolon, because there's nowhere to put it. | |
681 | // See issue #81943. | |
682 | && !in_external_macro(self.tcx.sess, expression.span) => | |
6a06907d | 683 | { |
923072b8 FG |
684 | if needs_block { |
685 | err.multipart_suggestion( | |
686 | "consider using a semicolon here", | |
687 | vec![ | |
688 | (expression.span.shrink_to_lo(), "{ ".to_owned()), | |
689 | (expression.span.shrink_to_hi(), "; }".to_owned()), | |
690 | ], | |
691 | Applicability::MachineApplicable, | |
692 | ); | |
693 | } else { | |
694 | err.span_suggestion( | |
695 | expression.span.shrink_to_hi(), | |
696 | "consider using a semicolon here", | |
697 | ";", | |
698 | Applicability::MachineApplicable, | |
699 | ); | |
700 | } | |
29967ef6 XL |
701 | } |
702 | _ => (), | |
703 | } | |
704 | } | |
705 | } | |
706 | ||
707 | /// A possible error is to forget to add a return type that is needed: | |
708 | /// | |
04454e1e FG |
709 | /// ```compile_fail,E0308 |
710 | /// # fn bar_that_returns_u32() -> u32 { 4 } | |
29967ef6 XL |
711 | /// fn foo() { |
712 | /// bar_that_returns_u32() | |
713 | /// } | |
714 | /// ``` | |
715 | /// | |
716 | /// This routine checks if the return type is left as default, the method is not part of an | |
717 | /// `impl` block and that it isn't the `main` method. If so, it suggests setting the return | |
718 | /// type. | |
719 | pub(in super::super) fn suggest_missing_return_type( | |
720 | &self, | |
5e7ed085 | 721 | err: &mut Diagnostic, |
29967ef6 XL |
722 | fn_decl: &hir::FnDecl<'_>, |
723 | expected: Ty<'tcx>, | |
724 | found: Ty<'tcx>, | |
725 | can_suggest: bool, | |
136023e0 | 726 | fn_id: hir::HirId, |
29967ef6 | 727 | ) -> bool { |
5e7ed085 FG |
728 | let found = |
729 | self.resolve_numeric_literals_with_default(self.resolve_vars_if_possible(found)); | |
29967ef6 XL |
730 | // Only suggest changing the return type for methods that |
731 | // haven't set a return type at all (and aren't `fn main()` or an impl). | |
f2b60f7d FG |
732 | match &fn_decl.output { |
733 | &hir::FnRetTy::DefaultReturn(span) if expected.is_unit() && !can_suggest => { | |
29967ef6 | 734 | // `fn main()` must return `()`, do not suggest changing return type |
04454e1e | 735 | err.subdiagnostic(ExpectedReturnTypeLabel::Unit { span }); |
f2b60f7d FG |
736 | return true; |
737 | } | |
738 | &hir::FnRetTy::DefaultReturn(span) if expected.is_unit() => { | |
739 | if found.is_suggestable(self.tcx, false) { | |
740 | err.subdiagnostic(AddReturnTypeSuggestion::Add { span, found: found.to_string() }); | |
741 | return true; | |
742 | } else if let ty::Closure(_, substs) = found.kind() | |
743 | // FIXME(compiler-errors): Get better at printing binders... | |
744 | && let closure = substs.as_closure() | |
745 | && closure.sig().is_suggestable(self.tcx, false) | |
746 | { | |
747 | err.subdiagnostic(AddReturnTypeSuggestion::Add { span, found: closure.print_as_impl_trait().to_string() }); | |
748 | return true; | |
749 | } else { | |
750 | // FIXME: if `found` could be `impl Iterator` we should suggest that. | |
751 | err.subdiagnostic(AddReturnTypeSuggestion::MissingHere { span }); | |
752 | return true | |
753 | } | |
29967ef6 | 754 | } |
f2b60f7d | 755 | &hir::FnRetTy::Return(ref ty) => { |
29967ef6 XL |
756 | // Only point to return type if the expected type is the return type, as if they |
757 | // are not, the expectation must have been caused by something else. | |
758 | debug!("suggest_missing_return_type: return type {:?} node {:?}", ty, ty.kind); | |
04454e1e | 759 | let span = ty.span; |
6a06907d | 760 | let ty = <dyn AstConv<'_>>::ast_ty_to_ty(self, ty); |
29967ef6 XL |
761 | debug!("suggest_missing_return_type: return type {:?}", ty); |
762 | debug!("suggest_missing_return_type: expected type {:?}", ty); | |
136023e0 | 763 | let bound_vars = self.tcx.late_bound_vars(fn_id); |
c295e0f8 | 764 | let ty = Binder::bind_with_vars(ty, bound_vars); |
487cf647 | 765 | let ty = self.normalize(span, ty); |
c295e0f8 | 766 | let ty = self.tcx.erase_late_bound_regions(ty); |
136023e0 | 767 | if self.can_coerce(expected, ty) { |
04454e1e | 768 | err.subdiagnostic(ExpectedReturnTypeLabel::Other { span, expected }); |
5099ac24 | 769 | self.try_suggest_return_impl_trait(err, expected, ty, fn_id); |
29967ef6 XL |
770 | return true; |
771 | } | |
29967ef6 | 772 | } |
f2b60f7d | 773 | _ => {} |
29967ef6 | 774 | } |
f2b60f7d | 775 | false |
29967ef6 XL |
776 | } |
777 | ||
5099ac24 FG |
778 | /// check whether the return type is a generic type with a trait bound |
779 | /// only suggest this if the generic param is not present in the arguments | |
780 | /// if this is true, hint them towards changing the return type to `impl Trait` | |
04454e1e | 781 | /// ```compile_fail,E0308 |
5099ac24 FG |
782 | /// fn cant_name_it<T: Fn() -> u32>() -> T { |
783 | /// || 3 | |
784 | /// } | |
785 | /// ``` | |
786 | fn try_suggest_return_impl_trait( | |
787 | &self, | |
5e7ed085 | 788 | err: &mut Diagnostic, |
5099ac24 FG |
789 | expected: Ty<'tcx>, |
790 | found: Ty<'tcx>, | |
791 | fn_id: hir::HirId, | |
792 | ) { | |
793 | // Only apply the suggestion if: | |
794 | // - the return type is a generic parameter | |
795 | // - the generic param is not used as a fn param | |
796 | // - the generic param has at least one bound | |
797 | // - the generic param doesn't appear in any other bounds where it's not the Self type | |
798 | // Suggest: | |
799 | // - Changing the return type to be `impl <all bounds>` | |
800 | ||
801 | debug!("try_suggest_return_impl_trait, expected = {:?}, found = {:?}", expected, found); | |
802 | ||
803 | let ty::Param(expected_ty_as_param) = expected.kind() else { return }; | |
804 | ||
805 | let fn_node = self.tcx.hir().find(fn_id); | |
806 | ||
807 | let Some(hir::Node::Item(hir::Item { | |
808 | kind: | |
809 | hir::ItemKind::Fn( | |
810 | hir::FnSig { decl: hir::FnDecl { inputs: fn_parameters, output: fn_return, .. }, .. }, | |
04454e1e | 811 | hir::Generics { params, predicates, .. }, |
5099ac24 FG |
812 | _body_id, |
813 | ), | |
814 | .. | |
815 | })) = fn_node else { return }; | |
816 | ||
04454e1e FG |
817 | if params.get(expected_ty_as_param.index as usize).is_none() { |
818 | return; | |
819 | }; | |
5099ac24 FG |
820 | |
821 | // get all where BoundPredicates here, because they are used in to cases below | |
04454e1e | 822 | let where_predicates = predicates |
5099ac24 FG |
823 | .iter() |
824 | .filter_map(|p| match p { | |
825 | WherePredicate::BoundPredicate(hir::WhereBoundPredicate { | |
826 | bounds, | |
827 | bounded_ty, | |
828 | .. | |
829 | }) => { | |
830 | // FIXME: Maybe these calls to `ast_ty_to_ty` can be removed (and the ones below) | |
831 | let ty = <dyn AstConv<'_>>::ast_ty_to_ty(self, bounded_ty); | |
832 | Some((ty, bounds)) | |
833 | } | |
834 | _ => None, | |
835 | }) | |
836 | .map(|(ty, bounds)| match ty.kind() { | |
837 | ty::Param(param_ty) if param_ty == expected_ty_as_param => Ok(Some(bounds)), | |
838 | // check whether there is any predicate that contains our `T`, like `Option<T>: Send` | |
839 | _ => match ty.contains(expected) { | |
840 | true => Err(()), | |
841 | false => Ok(None), | |
842 | }, | |
843 | }) | |
844 | .collect::<Result<Vec<_>, _>>(); | |
845 | ||
5e7ed085 | 846 | let Ok(where_predicates) = where_predicates else { return }; |
5099ac24 FG |
847 | |
848 | // now get all predicates in the same types as the where bounds, so we can chain them | |
849 | let predicates_from_where = | |
04454e1e | 850 | where_predicates.iter().flatten().flat_map(|bounds| bounds.iter()); |
5099ac24 FG |
851 | |
852 | // extract all bounds from the source code using their spans | |
04454e1e | 853 | let all_matching_bounds_strs = predicates_from_where |
5099ac24 FG |
854 | .filter_map(|bound| match bound { |
855 | GenericBound::Trait(_, _) => { | |
856 | self.tcx.sess.source_map().span_to_snippet(bound.span()).ok() | |
857 | } | |
858 | _ => None, | |
859 | }) | |
860 | .collect::<Vec<String>>(); | |
861 | ||
862 | if all_matching_bounds_strs.len() == 0 { | |
863 | return; | |
864 | } | |
865 | ||
866 | let all_bounds_str = all_matching_bounds_strs.join(" + "); | |
867 | ||
868 | let ty_param_used_in_fn_params = fn_parameters.iter().any(|param| { | |
869 | let ty = <dyn AstConv<'_>>::ast_ty_to_ty(self, param); | |
870 | matches!(ty.kind(), ty::Param(fn_param_ty_param) if expected_ty_as_param == fn_param_ty_param) | |
871 | }); | |
872 | ||
873 | if ty_param_used_in_fn_params { | |
874 | return; | |
875 | } | |
876 | ||
877 | err.span_suggestion( | |
878 | fn_return.span(), | |
879 | "consider using an impl return type", | |
880 | format!("impl {}", all_bounds_str), | |
881 | Applicability::MaybeIncorrect, | |
882 | ); | |
883 | } | |
884 | ||
cdc7bbd5 | 885 | pub(in super::super) fn suggest_missing_break_or_return_expr( |
6a06907d | 886 | &self, |
5e7ed085 | 887 | err: &mut Diagnostic, |
6a06907d XL |
888 | expr: &'tcx hir::Expr<'tcx>, |
889 | fn_decl: &hir::FnDecl<'_>, | |
890 | expected: Ty<'tcx>, | |
891 | found: Ty<'tcx>, | |
cdc7bbd5 XL |
892 | id: hir::HirId, |
893 | fn_id: hir::HirId, | |
6a06907d XL |
894 | ) { |
895 | if !expected.is_unit() { | |
896 | return; | |
897 | } | |
898 | let found = self.resolve_vars_with_obligations(found); | |
cdc7bbd5 XL |
899 | |
900 | let in_loop = self.is_loop(id) | |
901 | || self.tcx.hir().parent_iter(id).any(|(parent_id, _)| self.is_loop(parent_id)); | |
902 | ||
903 | let in_local_statement = self.is_local_statement(id) | |
904 | || self | |
905 | .tcx | |
906 | .hir() | |
907 | .parent_iter(id) | |
908 | .any(|(parent_id, _)| self.is_local_statement(parent_id)); | |
909 | ||
910 | if in_loop && in_local_statement { | |
911 | err.multipart_suggestion( | |
912 | "you might have meant to break the loop with this value", | |
913 | vec![ | |
914 | (expr.span.shrink_to_lo(), "break ".to_string()), | |
915 | (expr.span.shrink_to_hi(), ";".to_string()), | |
916 | ], | |
917 | Applicability::MaybeIncorrect, | |
918 | ); | |
919 | return; | |
920 | } | |
921 | ||
6a06907d XL |
922 | if let hir::FnRetTy::Return(ty) = fn_decl.output { |
923 | let ty = <dyn AstConv<'_>>::ast_ty_to_ty(self, ty); | |
cdc7bbd5 XL |
924 | let bound_vars = self.tcx.late_bound_vars(fn_id); |
925 | let ty = self.tcx.erase_late_bound_regions(Binder::bind_with_vars(ty, bound_vars)); | |
5099ac24 | 926 | let ty = match self.tcx.asyncness(fn_id.owner) { |
487cf647 FG |
927 | hir::IsAsync::Async => self.get_impl_future_output_ty(ty).unwrap_or_else(|| { |
928 | span_bug!(fn_decl.output.span(), "failed to get output type of async function") | |
929 | }), | |
5099ac24 FG |
930 | hir::IsAsync::NotAsync => ty, |
931 | }; | |
487cf647 | 932 | let ty = self.normalize(expr.span, ty); |
6a06907d XL |
933 | if self.can_coerce(found, ty) { |
934 | err.multipart_suggestion( | |
935 | "you might have meant to return this value", | |
936 | vec![ | |
937 | (expr.span.shrink_to_lo(), "return ".to_string()), | |
938 | (expr.span.shrink_to_hi(), ";".to_string()), | |
939 | ], | |
940 | Applicability::MaybeIncorrect, | |
941 | ); | |
942 | } | |
943 | } | |
944 | } | |
945 | ||
29967ef6 XL |
946 | pub(in super::super) fn suggest_missing_parentheses( |
947 | &self, | |
5e7ed085 | 948 | err: &mut Diagnostic, |
29967ef6 | 949 | expr: &hir::Expr<'_>, |
2b03887a | 950 | ) -> bool { |
29967ef6 XL |
951 | let sp = self.tcx.sess.source_map().start_point(expr.span); |
952 | if let Some(sp) = self.tcx.sess.parse_sess.ambiguous_block_expr_parse.borrow().get(&sp) { | |
953 | // `{ 42 } &&x` (#61475) or `{ 42 } && if x { 1 } else { 0 }` | |
2b03887a FG |
954 | err.subdiagnostic(ExprParenthesesNeeded::surrounding(*sp)); |
955 | true | |
956 | } else { | |
957 | false | |
29967ef6 XL |
958 | } |
959 | } | |
cdc7bbd5 | 960 | |
5e7ed085 FG |
961 | /// Given an expression type mismatch, peel any `&` expressions until we get to |
962 | /// a block expression, and then suggest replacing the braces with square braces | |
963 | /// if it was possibly mistaken array syntax. | |
964 | pub(crate) fn suggest_block_to_brackets_peeling_refs( | |
965 | &self, | |
966 | diag: &mut Diagnostic, | |
967 | mut expr: &hir::Expr<'_>, | |
968 | mut expr_ty: Ty<'tcx>, | |
969 | mut expected_ty: Ty<'tcx>, | |
2b03887a | 970 | ) -> bool { |
5e7ed085 FG |
971 | loop { |
972 | match (&expr.kind, expr_ty.kind(), expected_ty.kind()) { | |
973 | ( | |
974 | hir::ExprKind::AddrOf(_, _, inner_expr), | |
975 | ty::Ref(_, inner_expr_ty, _), | |
976 | ty::Ref(_, inner_expected_ty, _), | |
977 | ) => { | |
978 | expr = *inner_expr; | |
979 | expr_ty = *inner_expr_ty; | |
980 | expected_ty = *inner_expected_ty; | |
981 | } | |
982 | (hir::ExprKind::Block(blk, _), _, _) => { | |
983 | self.suggest_block_to_brackets(diag, *blk, expr_ty, expected_ty); | |
2b03887a | 984 | break true; |
5e7ed085 | 985 | } |
2b03887a | 986 | _ => break false, |
5e7ed085 FG |
987 | } |
988 | } | |
989 | } | |
990 | ||
f2b60f7d FG |
991 | pub(crate) fn suggest_copied_or_cloned( |
992 | &self, | |
993 | diag: &mut Diagnostic, | |
994 | expr: &hir::Expr<'_>, | |
995 | expr_ty: Ty<'tcx>, | |
996 | expected_ty: Ty<'tcx>, | |
2b03887a FG |
997 | ) -> bool { |
998 | let ty::Adt(adt_def, substs) = expr_ty.kind() else { return false; }; | |
999 | let ty::Adt(expected_adt_def, expected_substs) = expected_ty.kind() else { return false; }; | |
f2b60f7d | 1000 | if adt_def != expected_adt_def { |
2b03887a | 1001 | return false; |
f2b60f7d FG |
1002 | } |
1003 | ||
1004 | let mut suggest_copied_or_cloned = || { | |
1005 | let expr_inner_ty = substs.type_at(0); | |
1006 | let expected_inner_ty = expected_substs.type_at(0); | |
1007 | if let ty::Ref(_, ty, hir::Mutability::Not) = expr_inner_ty.kind() | |
1008 | && self.can_eq(self.param_env, *ty, expected_inner_ty).is_ok() | |
1009 | { | |
1010 | let def_path = self.tcx.def_path_str(adt_def.did()); | |
1011 | if self.type_is_copy_modulo_regions(self.param_env, *ty, expr.span) { | |
1012 | diag.span_suggestion_verbose( | |
1013 | expr.span.shrink_to_hi(), | |
1014 | format!( | |
1015 | "use `{def_path}::copied` to copy the value inside the `{def_path}`" | |
1016 | ), | |
1017 | ".copied()", | |
1018 | Applicability::MachineApplicable, | |
1019 | ); | |
2b03887a | 1020 | return true; |
f2b60f7d FG |
1021 | } else if let Some(clone_did) = self.tcx.lang_items().clone_trait() |
1022 | && rustc_trait_selection::traits::type_known_to_meet_bound_modulo_regions( | |
1023 | self, | |
1024 | self.param_env, | |
1025 | *ty, | |
1026 | clone_did, | |
1027 | expr.span | |
1028 | ) | |
1029 | { | |
1030 | diag.span_suggestion_verbose( | |
1031 | expr.span.shrink_to_hi(), | |
1032 | format!( | |
1033 | "use `{def_path}::cloned` to clone the value inside the `{def_path}`" | |
1034 | ), | |
1035 | ".cloned()", | |
1036 | Applicability::MachineApplicable, | |
1037 | ); | |
2b03887a | 1038 | return true; |
f2b60f7d FG |
1039 | } |
1040 | } | |
2b03887a | 1041 | false |
f2b60f7d FG |
1042 | }; |
1043 | ||
1044 | if let Some(result_did) = self.tcx.get_diagnostic_item(sym::Result) | |
1045 | && adt_def.did() == result_did | |
1046 | // Check that the error types are equal | |
1047 | && self.can_eq(self.param_env, substs.type_at(1), expected_substs.type_at(1)).is_ok() | |
1048 | { | |
2b03887a | 1049 | return suggest_copied_or_cloned(); |
f2b60f7d FG |
1050 | } else if let Some(option_did) = self.tcx.get_diagnostic_item(sym::Option) |
1051 | && adt_def.did() == option_did | |
1052 | { | |
2b03887a | 1053 | return suggest_copied_or_cloned(); |
f2b60f7d | 1054 | } |
2b03887a FG |
1055 | |
1056 | false | |
1057 | } | |
1058 | ||
1059 | pub(crate) fn suggest_into( | |
1060 | &self, | |
1061 | diag: &mut Diagnostic, | |
1062 | expr: &hir::Expr<'_>, | |
1063 | expr_ty: Ty<'tcx>, | |
1064 | expected_ty: Ty<'tcx>, | |
1065 | ) -> bool { | |
1066 | let expr = expr.peel_blocks(); | |
1067 | ||
1068 | // We have better suggestions for scalar interconversions... | |
1069 | if expr_ty.is_scalar() && expected_ty.is_scalar() { | |
1070 | return false; | |
1071 | } | |
1072 | ||
1073 | // Don't suggest turning a block into another type (e.g. `{}.into()`) | |
1074 | if matches!(expr.kind, hir::ExprKind::Block(..)) { | |
1075 | return false; | |
1076 | } | |
1077 | ||
1078 | // We'll later suggest `.as_ref` when noting the type error, | |
1079 | // so skip if we will suggest that instead. | |
1080 | if self.err_ctxt().should_suggest_as_ref(expected_ty, expr_ty).is_some() { | |
1081 | return false; | |
1082 | } | |
1083 | ||
1084 | if let Some(into_def_id) = self.tcx.get_diagnostic_item(sym::Into) | |
1085 | && self.predicate_must_hold_modulo_regions(&traits::Obligation::new( | |
487cf647 | 1086 | self.tcx, |
2b03887a FG |
1087 | self.misc(expr.span), |
1088 | self.param_env, | |
487cf647 FG |
1089 | ty::Binder::dummy(self.tcx.mk_trait_ref( |
1090 | into_def_id, | |
1091 | [expr_ty, expected_ty] | |
1092 | )), | |
2b03887a FG |
1093 | )) |
1094 | { | |
1095 | let sugg = if expr.precedence().order() >= PREC_POSTFIX { | |
1096 | vec![(expr.span.shrink_to_hi(), ".into()".to_owned())] | |
1097 | } else { | |
1098 | vec![(expr.span.shrink_to_lo(), "(".to_owned()), (expr.span.shrink_to_hi(), ").into()".to_owned())] | |
1099 | }; | |
1100 | diag.multipart_suggestion( | |
1101 | format!("call `Into::into` on this expression to convert `{expr_ty}` into `{expected_ty}`"), | |
1102 | sugg, | |
1103 | Applicability::MaybeIncorrect | |
1104 | ); | |
1105 | return true; | |
1106 | } | |
1107 | ||
1108 | false | |
f2b60f7d FG |
1109 | } |
1110 | ||
487cf647 FG |
1111 | /// When expecting a `bool` and finding an `Option`, suggests using `let Some(..)` or `.is_some()` |
1112 | pub(crate) fn suggest_option_to_bool( | |
1113 | &self, | |
1114 | diag: &mut Diagnostic, | |
1115 | expr: &hir::Expr<'_>, | |
1116 | expr_ty: Ty<'tcx>, | |
1117 | expected_ty: Ty<'tcx>, | |
1118 | ) -> bool { | |
1119 | if !expected_ty.is_bool() { | |
1120 | return false; | |
1121 | } | |
1122 | ||
1123 | let ty::Adt(def, _) = expr_ty.peel_refs().kind() else { return false; }; | |
1124 | if !self.tcx.is_diagnostic_item(sym::Option, def.did()) { | |
1125 | return false; | |
1126 | } | |
1127 | ||
1128 | let hir = self.tcx.hir(); | |
1129 | let cond_parent = hir.parent_iter(expr.hir_id).skip_while(|(_, node)| { | |
1130 | matches!(node, hir::Node::Expr(hir::Expr { kind: hir::ExprKind::Binary(op, _, _), .. }) if op.node == hir::BinOpKind::And) | |
1131 | }).next(); | |
1132 | // Don't suggest: | |
1133 | // `let Some(_) = a.is_some() && b` | |
1134 | // ++++++++++ | |
1135 | // since the user probably just misunderstood how `let else` | |
1136 | // and `&&` work together. | |
1137 | if let Some((_, hir::Node::Local(local))) = cond_parent | |
1138 | && let hir::PatKind::Path(qpath) | hir::PatKind::TupleStruct(qpath, _, _) = &local.pat.kind | |
1139 | && let hir::QPath::Resolved(None, path) = qpath | |
1140 | && let Some(did) = path.res.opt_def_id() | |
1141 | .and_then(|did| self.tcx.opt_parent(did)) | |
1142 | .and_then(|did| self.tcx.opt_parent(did)) | |
1143 | && self.tcx.is_diagnostic_item(sym::Option, did) | |
1144 | { | |
1145 | return false; | |
1146 | } | |
1147 | ||
1148 | diag.span_suggestion( | |
1149 | expr.span.shrink_to_hi(), | |
1150 | "use `Option::is_some` to test if the `Option` has a value", | |
1151 | ".is_some()", | |
1152 | Applicability::MachineApplicable, | |
1153 | ); | |
1154 | ||
1155 | true | |
1156 | } | |
1157 | ||
5e7ed085 FG |
1158 | /// Suggest wrapping the block in square brackets instead of curly braces |
1159 | /// in case the block was mistaken array syntax, e.g. `{ 1 }` -> `[ 1 ]`. | |
1160 | pub(crate) fn suggest_block_to_brackets( | |
1161 | &self, | |
1162 | diag: &mut Diagnostic, | |
1163 | blk: &hir::Block<'_>, | |
1164 | blk_ty: Ty<'tcx>, | |
1165 | expected_ty: Ty<'tcx>, | |
1166 | ) { | |
1167 | if let ty::Slice(elem_ty) | ty::Array(elem_ty, _) = expected_ty.kind() { | |
1168 | if self.can_coerce(blk_ty, *elem_ty) | |
1169 | && blk.stmts.is_empty() | |
1170 | && blk.rules == hir::BlockCheckMode::DefaultBlock | |
1171 | { | |
1172 | let source_map = self.tcx.sess.source_map(); | |
1173 | if let Ok(snippet) = source_map.span_to_snippet(blk.span) { | |
1174 | if snippet.starts_with('{') && snippet.ends_with('}') { | |
1175 | diag.multipart_suggestion_verbose( | |
1176 | "to create an array, use square brackets instead of curly braces", | |
1177 | vec![ | |
1178 | ( | |
1179 | blk.span | |
1180 | .shrink_to_lo() | |
1181 | .with_hi(rustc_span::BytePos(blk.span.lo().0 + 1)), | |
1182 | "[".to_string(), | |
1183 | ), | |
1184 | ( | |
1185 | blk.span | |
1186 | .shrink_to_hi() | |
1187 | .with_lo(rustc_span::BytePos(blk.span.hi().0 - 1)), | |
1188 | "]".to_string(), | |
1189 | ), | |
1190 | ], | |
1191 | Applicability::MachineApplicable, | |
1192 | ); | |
1193 | } | |
1194 | } | |
1195 | } | |
1196 | } | |
1197 | } | |
1198 | ||
487cf647 FG |
1199 | #[instrument(skip(self, err))] |
1200 | pub(crate) fn suggest_floating_point_literal( | |
1201 | &self, | |
1202 | err: &mut Diagnostic, | |
1203 | expr: &hir::Expr<'_>, | |
1204 | expected_ty: Ty<'tcx>, | |
1205 | ) -> bool { | |
1206 | if !expected_ty.is_floating_point() { | |
1207 | return false; | |
1208 | } | |
1209 | match expr.kind { | |
1210 | ExprKind::Struct(QPath::LangItem(LangItem::Range, ..), [start, end], _) => { | |
1211 | err.span_suggestion_verbose( | |
1212 | start.span.shrink_to_hi().with_hi(end.span.lo()), | |
1213 | "remove the unnecessary `.` operator for a floating point literal", | |
1214 | '.', | |
1215 | Applicability::MaybeIncorrect, | |
1216 | ); | |
1217 | true | |
1218 | } | |
1219 | ExprKind::Struct(QPath::LangItem(LangItem::RangeFrom, ..), [start], _) => { | |
1220 | err.span_suggestion_verbose( | |
1221 | expr.span.with_lo(start.span.hi()), | |
1222 | "remove the unnecessary `.` operator for a floating point literal", | |
1223 | '.', | |
1224 | Applicability::MaybeIncorrect, | |
1225 | ); | |
1226 | true | |
1227 | } | |
1228 | ExprKind::Struct(QPath::LangItem(LangItem::RangeTo, ..), [end], _) => { | |
1229 | err.span_suggestion_verbose( | |
1230 | expr.span.until(end.span), | |
1231 | "remove the unnecessary `.` operator and add an integer part for a floating point literal", | |
1232 | "0.", | |
1233 | Applicability::MaybeIncorrect, | |
1234 | ); | |
1235 | true | |
1236 | } | |
1237 | _ => false, | |
1238 | } | |
1239 | } | |
1240 | ||
cdc7bbd5 XL |
1241 | fn is_loop(&self, id: hir::HirId) -> bool { |
1242 | let node = self.tcx.hir().get(id); | |
1243 | matches!(node, Node::Expr(Expr { kind: ExprKind::Loop(..), .. })) | |
1244 | } | |
1245 | ||
1246 | fn is_local_statement(&self, id: hir::HirId) -> bool { | |
1247 | let node = self.tcx.hir().get(id); | |
1248 | matches!(node, Node::Stmt(Stmt { kind: StmtKind::Local(..), .. })) | |
1249 | } | |
04454e1e FG |
1250 | |
1251 | /// Suggest that `&T` was cloned instead of `T` because `T` does not implement `Clone`, | |
1252 | /// which is a side-effect of autoref. | |
1253 | pub(crate) fn note_type_is_not_clone( | |
1254 | &self, | |
1255 | diag: &mut Diagnostic, | |
1256 | expected_ty: Ty<'tcx>, | |
1257 | found_ty: Ty<'tcx>, | |
1258 | expr: &hir::Expr<'_>, | |
1259 | ) { | |
f2b60f7d | 1260 | let hir::ExprKind::MethodCall(segment, callee_expr, &[], _) = expr.kind else { return; }; |
04454e1e FG |
1261 | let Some(clone_trait_did) = self.tcx.lang_items().clone_trait() else { return; }; |
1262 | let ty::Ref(_, pointee_ty, _) = found_ty.kind() else { return }; | |
1263 | let results = self.typeck_results.borrow(); | |
1264 | // First, look for a `Clone::clone` call | |
1265 | if segment.ident.name == sym::clone | |
1266 | && results.type_dependent_def_id(expr.hir_id).map_or( | |
1267 | false, | |
1268 | |did| { | |
064997fb FG |
1269 | let assoc_item = self.tcx.associated_item(did); |
1270 | assoc_item.container == ty::AssocItemContainer::TraitContainer | |
1271 | && assoc_item.container_id(self.tcx) == clone_trait_did | |
04454e1e FG |
1272 | }, |
1273 | ) | |
1274 | // If that clone call hasn't already dereferenced the self type (i.e. don't give this | |
1275 | // diagnostic in cases where we have `(&&T).clone()` and we expect `T`). | |
1276 | && !results.expr_adjustments(callee_expr).iter().any(|adj| matches!(adj.kind, ty::adjustment::Adjust::Deref(..))) | |
1277 | // Check that we're in fact trying to clone into the expected type | |
1278 | && self.can_coerce(*pointee_ty, expected_ty) | |
1279 | // And the expected type doesn't implement `Clone` | |
1280 | && !self.predicate_must_hold_considering_regions(&traits::Obligation { | |
1281 | cause: traits::ObligationCause::dummy(), | |
1282 | param_env: self.param_env, | |
1283 | recursion_depth: 0, | |
1284 | predicate: ty::Binder::dummy(ty::TraitRef { | |
1285 | def_id: clone_trait_did, | |
1286 | substs: self.tcx.mk_substs([expected_ty.into()].iter()), | |
1287 | }) | |
1288 | .without_const() | |
1289 | .to_predicate(self.tcx), | |
1290 | }) | |
1291 | { | |
1292 | diag.span_note( | |
1293 | callee_expr.span, | |
1294 | &format!( | |
1295 | "`{expected_ty}` does not implement `Clone`, so `{found_ty}` was cloned instead" | |
1296 | ), | |
1297 | ); | |
1298 | } | |
1299 | } | |
064997fb FG |
1300 | |
1301 | /// A common error is to add an extra semicolon: | |
1302 | /// | |
1303 | /// ```compile_fail,E0308 | |
1304 | /// fn foo() -> usize { | |
1305 | /// 22; | |
1306 | /// } | |
1307 | /// ``` | |
1308 | /// | |
1309 | /// This routine checks if the final statement in a block is an | |
1310 | /// expression with an explicit semicolon whose type is compatible | |
1311 | /// with `expected_ty`. If so, it suggests removing the semicolon. | |
1312 | pub(crate) fn consider_removing_semicolon( | |
1313 | &self, | |
1314 | blk: &'tcx hir::Block<'tcx>, | |
1315 | expected_ty: Ty<'tcx>, | |
1316 | err: &mut Diagnostic, | |
1317 | ) -> bool { | |
2b03887a | 1318 | if let Some((span_semi, boxed)) = self.err_ctxt().could_remove_semicolon(blk, expected_ty) { |
064997fb FG |
1319 | if let StatementAsExpression::NeedsBoxing = boxed { |
1320 | err.span_suggestion_verbose( | |
1321 | span_semi, | |
1322 | "consider removing this semicolon and boxing the expression", | |
1323 | "", | |
1324 | Applicability::HasPlaceholders, | |
1325 | ); | |
1326 | } else { | |
1327 | err.span_suggestion_short( | |
1328 | span_semi, | |
2b03887a | 1329 | "remove this semicolon to return this value", |
064997fb FG |
1330 | "", |
1331 | Applicability::MachineApplicable, | |
1332 | ); | |
1333 | } | |
1334 | true | |
1335 | } else { | |
1336 | false | |
1337 | } | |
1338 | } | |
29967ef6 | 1339 | } |