]>
Commit | Line | Data |
---|---|---|
29967ef6 XL |
1 | use super::FnCtxt; |
2 | use crate::astconv::AstConv; | |
3 | ||
4 | use rustc_ast::util::parser::ExprPrecedence; | |
5869c6ff | 5 | use rustc_span::{self, MultiSpan, Span}; |
29967ef6 XL |
6 | |
7 | use rustc_errors::{Applicability, DiagnosticBuilder}; | |
8 | use rustc_hir as hir; | |
9 | use rustc_hir::def::{CtorOf, DefKind}; | |
10 | use rustc_hir::lang_items::LangItem; | |
11 | use rustc_hir::{ExprKind, ItemKind, Node}; | |
12 | use rustc_infer::infer; | |
6a06907d XL |
13 | use rustc_middle::lint::in_external_macro; |
14 | use rustc_middle::ty::{self, Binder, Ty}; | |
29967ef6 XL |
15 | use rustc_span::symbol::kw; |
16 | ||
17 | use std::iter; | |
18 | ||
19 | impl<'a, 'tcx> FnCtxt<'a, 'tcx> { | |
20 | pub(in super::super) fn suggest_semicolon_at_end( | |
21 | &self, | |
22 | span: Span, | |
23 | err: &mut DiagnosticBuilder<'_>, | |
24 | ) { | |
25 | err.span_suggestion_short( | |
26 | span.shrink_to_hi(), | |
27 | "consider using a semicolon here", | |
28 | ";".to_string(), | |
29 | Applicability::MachineApplicable, | |
30 | ); | |
31 | } | |
32 | ||
33 | /// On implicit return expressions with mismatched types, provides the following suggestions: | |
34 | /// | |
35 | /// - Points out the method's return type as the reason for the expected type. | |
36 | /// - Possible missing semicolon. | |
37 | /// - Possible missing return type if the return type is the default, and not `fn main()`. | |
38 | pub fn suggest_mismatched_types_on_tail( | |
39 | &self, | |
40 | err: &mut DiagnosticBuilder<'_>, | |
41 | expr: &'tcx hir::Expr<'tcx>, | |
42 | expected: Ty<'tcx>, | |
43 | found: Ty<'tcx>, | |
44 | cause_span: Span, | |
45 | blk_id: hir::HirId, | |
46 | ) -> bool { | |
47 | let expr = expr.peel_drop_temps(); | |
6a06907d XL |
48 | // If the expression is from an external macro, then do not suggest |
49 | // adding a semicolon, because there's nowhere to put it. | |
50 | // See issue #81943. | |
51 | if expr.can_have_side_effects() && !in_external_macro(self.tcx.sess, cause_span) { | |
52 | self.suggest_missing_semicolon(err, expr, expected, cause_span); | |
53 | } | |
29967ef6 XL |
54 | let mut pointing_at_return_type = false; |
55 | if let Some((fn_decl, can_suggest)) = self.get_fn_decl(blk_id) { | |
56 | pointing_at_return_type = | |
57 | self.suggest_missing_return_type(err, &fn_decl, expected, found, can_suggest); | |
6a06907d | 58 | self.suggest_missing_return_expr(err, expr, &fn_decl, expected, found); |
29967ef6 XL |
59 | } |
60 | pointing_at_return_type | |
61 | } | |
62 | ||
63 | /// When encountering an fn-like ctor that needs to unify with a value, check whether calling | |
64 | /// the ctor would successfully solve the type mismatch and if so, suggest it: | |
65 | /// ``` | |
66 | /// fn foo(x: usize) -> usize { x } | |
67 | /// let x: usize = foo; // suggest calling the `foo` function: `foo(42)` | |
68 | /// ``` | |
69 | fn suggest_fn_call( | |
70 | &self, | |
71 | err: &mut DiagnosticBuilder<'_>, | |
72 | expr: &hir::Expr<'_>, | |
73 | expected: Ty<'tcx>, | |
74 | found: Ty<'tcx>, | |
75 | ) -> bool { | |
76 | let hir = self.tcx.hir(); | |
77 | let (def_id, sig) = match *found.kind() { | |
78 | ty::FnDef(def_id, _) => (def_id, found.fn_sig(self.tcx)), | |
79 | ty::Closure(def_id, substs) => (def_id, substs.as_closure().sig()), | |
80 | _ => return false, | |
81 | }; | |
82 | ||
fc512014 XL |
83 | let sig = self.replace_bound_vars_with_fresh_vars(expr.span, infer::FnCall, sig).0; |
84 | let sig = self.normalize_associated_types_in(expr.span, sig); | |
29967ef6 XL |
85 | if self.can_coerce(sig.output(), expected) { |
86 | let (mut sugg_call, applicability) = if sig.inputs().is_empty() { | |
87 | (String::new(), Applicability::MachineApplicable) | |
88 | } else { | |
89 | ("...".to_string(), Applicability::HasPlaceholders) | |
90 | }; | |
91 | let mut msg = "call this function"; | |
92 | match hir.get_if_local(def_id) { | |
93 | Some( | |
94 | Node::Item(hir::Item { kind: ItemKind::Fn(.., body_id), .. }) | |
95 | | Node::ImplItem(hir::ImplItem { | |
96 | kind: hir::ImplItemKind::Fn(_, body_id), .. | |
97 | }) | |
98 | | Node::TraitItem(hir::TraitItem { | |
99 | kind: hir::TraitItemKind::Fn(.., hir::TraitFn::Provided(body_id)), | |
100 | .. | |
101 | }), | |
102 | ) => { | |
103 | let body = hir.body(*body_id); | |
104 | sugg_call = body | |
105 | .params | |
106 | .iter() | |
107 | .map(|param| match ¶m.pat.kind { | |
108 | hir::PatKind::Binding(_, _, ident, None) | |
109 | if ident.name != kw::SelfLower => | |
110 | { | |
111 | ident.to_string() | |
112 | } | |
113 | _ => "_".to_string(), | |
114 | }) | |
115 | .collect::<Vec<_>>() | |
116 | .join(", "); | |
117 | } | |
118 | Some(Node::Expr(hir::Expr { | |
119 | kind: ExprKind::Closure(_, _, body_id, _, _), | |
120 | span: full_closure_span, | |
121 | .. | |
122 | })) => { | |
123 | if *full_closure_span == expr.span { | |
124 | return false; | |
125 | } | |
126 | msg = "call this closure"; | |
127 | let body = hir.body(*body_id); | |
128 | sugg_call = body | |
129 | .params | |
130 | .iter() | |
131 | .map(|param| match ¶m.pat.kind { | |
132 | hir::PatKind::Binding(_, _, ident, None) | |
133 | if ident.name != kw::SelfLower => | |
134 | { | |
135 | ident.to_string() | |
136 | } | |
137 | _ => "_".to_string(), | |
138 | }) | |
139 | .collect::<Vec<_>>() | |
140 | .join(", "); | |
141 | } | |
142 | Some(Node::Ctor(hir::VariantData::Tuple(fields, _))) => { | |
143 | sugg_call = fields.iter().map(|_| "_").collect::<Vec<_>>().join(", "); | |
144 | match def_id.as_local().map(|def_id| hir.def_kind(def_id)) { | |
145 | Some(DefKind::Ctor(hir::def::CtorOf::Variant, _)) => { | |
146 | msg = "instantiate this tuple variant"; | |
147 | } | |
148 | Some(DefKind::Ctor(CtorOf::Struct, _)) => { | |
149 | msg = "instantiate this tuple struct"; | |
150 | } | |
151 | _ => {} | |
152 | } | |
153 | } | |
154 | Some(Node::ForeignItem(hir::ForeignItem { | |
155 | kind: hir::ForeignItemKind::Fn(_, idents, _), | |
156 | .. | |
157 | })) => { | |
158 | sugg_call = idents | |
159 | .iter() | |
160 | .map(|ident| { | |
161 | if ident.name != kw::SelfLower { | |
162 | ident.to_string() | |
163 | } else { | |
164 | "_".to_string() | |
165 | } | |
166 | }) | |
167 | .collect::<Vec<_>>() | |
168 | .join(", ") | |
169 | } | |
170 | Some(Node::TraitItem(hir::TraitItem { | |
171 | kind: hir::TraitItemKind::Fn(.., hir::TraitFn::Required(idents)), | |
172 | .. | |
173 | })) => { | |
174 | sugg_call = idents | |
175 | .iter() | |
176 | .map(|ident| { | |
177 | if ident.name != kw::SelfLower { | |
178 | ident.to_string() | |
179 | } else { | |
180 | "_".to_string() | |
181 | } | |
182 | }) | |
183 | .collect::<Vec<_>>() | |
184 | .join(", ") | |
185 | } | |
186 | _ => {} | |
187 | } | |
188 | err.span_suggestion_verbose( | |
189 | expr.span.shrink_to_hi(), | |
190 | &format!("use parentheses to {}", msg), | |
191 | format!("({})", sugg_call), | |
192 | applicability, | |
193 | ); | |
194 | return true; | |
195 | } | |
196 | false | |
197 | } | |
198 | ||
199 | pub fn suggest_deref_ref_or_into( | |
200 | &self, | |
201 | err: &mut DiagnosticBuilder<'_>, | |
202 | expr: &hir::Expr<'_>, | |
203 | expected: Ty<'tcx>, | |
204 | found: Ty<'tcx>, | |
205 | expected_ty_expr: Option<&'tcx hir::Expr<'tcx>>, | |
206 | ) { | |
207 | if let Some((sp, msg, suggestion, applicability)) = self.check_ref(expr, found, expected) { | |
208 | err.span_suggestion(sp, msg, suggestion, applicability); | |
209 | } else if let (ty::FnDef(def_id, ..), true) = | |
210 | (&found.kind(), self.suggest_fn_call(err, expr, expected, found)) | |
211 | { | |
212 | if let Some(sp) = self.tcx.hir().span_if_local(*def_id) { | |
213 | let sp = self.sess().source_map().guess_head_span(sp); | |
214 | err.span_label(sp, &format!("{} defined here", found)); | |
215 | } | |
216 | } else if !self.check_for_cast(err, expr, found, expected, expected_ty_expr) { | |
217 | let is_struct_pat_shorthand_field = | |
218 | self.is_hir_id_from_struct_pattern_shorthand_field(expr.hir_id, expr.span); | |
219 | let methods = self.get_conversion_methods(expr.span, expected, found, expr.hir_id); | |
220 | if let Ok(expr_text) = self.sess().source_map().span_to_snippet(expr.span) { | |
221 | let mut suggestions = iter::repeat(&expr_text) | |
222 | .zip(methods.iter()) | |
223 | .filter_map(|(receiver, method)| { | |
224 | let method_call = format!(".{}()", method.ident); | |
225 | if receiver.ends_with(&method_call) { | |
226 | None // do not suggest code that is already there (#53348) | |
227 | } else { | |
228 | let method_call_list = [".to_vec()", ".to_string()"]; | |
229 | let sugg = if receiver.ends_with(".clone()") | |
230 | && method_call_list.contains(&method_call.as_str()) | |
231 | { | |
232 | let max_len = receiver.rfind('.').unwrap(); | |
233 | format!("{}{}", &receiver[..max_len], method_call) | |
234 | } else { | |
235 | if expr.precedence().order() < ExprPrecedence::MethodCall.order() { | |
236 | format!("({}){}", receiver, method_call) | |
237 | } else { | |
238 | format!("{}{}", receiver, method_call) | |
239 | } | |
240 | }; | |
241 | Some(if is_struct_pat_shorthand_field { | |
242 | format!("{}: {}", receiver, sugg) | |
243 | } else { | |
244 | sugg | |
245 | }) | |
246 | } | |
247 | }) | |
248 | .peekable(); | |
249 | if suggestions.peek().is_some() { | |
250 | err.span_suggestions( | |
251 | expr.span, | |
252 | "try using a conversion method", | |
253 | suggestions, | |
254 | Applicability::MaybeIncorrect, | |
255 | ); | |
256 | } | |
257 | } | |
258 | } | |
259 | } | |
260 | ||
261 | /// When encountering the expected boxed value allocated in the stack, suggest allocating it | |
262 | /// in the heap by calling `Box::new()`. | |
263 | pub(in super::super) fn suggest_boxing_when_appropriate( | |
264 | &self, | |
265 | err: &mut DiagnosticBuilder<'_>, | |
266 | expr: &hir::Expr<'_>, | |
267 | expected: Ty<'tcx>, | |
268 | found: Ty<'tcx>, | |
269 | ) { | |
270 | if self.tcx.hir().is_inside_const_context(expr.hir_id) { | |
271 | // Do not suggest `Box::new` in const context. | |
272 | return; | |
273 | } | |
274 | if !expected.is_box() || found.is_box() { | |
275 | return; | |
276 | } | |
277 | let boxed_found = self.tcx.mk_box(found); | |
278 | if let (true, Ok(snippet)) = ( | |
279 | self.can_coerce(boxed_found, expected), | |
280 | self.sess().source_map().span_to_snippet(expr.span), | |
281 | ) { | |
282 | err.span_suggestion( | |
283 | expr.span, | |
284 | "store this in the heap by calling `Box::new`", | |
285 | format!("Box::new({})", snippet), | |
286 | Applicability::MachineApplicable, | |
287 | ); | |
288 | err.note( | |
289 | "for more on the distinction between the stack and the heap, read \ | |
290 | https://doc.rust-lang.org/book/ch15-01-box.html, \ | |
291 | https://doc.rust-lang.org/rust-by-example/std/box.html, and \ | |
292 | https://doc.rust-lang.org/std/boxed/index.html", | |
293 | ); | |
294 | } | |
295 | } | |
296 | ||
5869c6ff XL |
297 | /// When encountering a closure that captures variables, where a FnPtr is expected, |
298 | /// suggest a non-capturing closure | |
299 | pub(in super::super) fn suggest_no_capture_closure( | |
300 | &self, | |
301 | err: &mut DiagnosticBuilder<'_>, | |
302 | expected: Ty<'tcx>, | |
303 | found: Ty<'tcx>, | |
304 | ) { | |
305 | if let (ty::FnPtr(_), ty::Closure(def_id, _)) = (expected.kind(), found.kind()) { | |
306 | if let Some(upvars) = self.tcx.upvars_mentioned(*def_id) { | |
307 | // Report upto four upvars being captured to reduce the amount error messages | |
308 | // reported back to the user. | |
309 | let spans_and_labels = upvars | |
310 | .iter() | |
311 | .take(4) | |
312 | .map(|(var_hir_id, upvar)| { | |
313 | let var_name = self.tcx.hir().name(*var_hir_id).to_string(); | |
314 | let msg = format!("`{}` captured here", var_name); | |
315 | (upvar.span, msg) | |
316 | }) | |
317 | .collect::<Vec<_>>(); | |
318 | ||
319 | let mut multi_span: MultiSpan = | |
320 | spans_and_labels.iter().map(|(sp, _)| *sp).collect::<Vec<_>>().into(); | |
321 | for (sp, label) in spans_and_labels { | |
322 | multi_span.push_span_label(sp, label); | |
323 | } | |
324 | err.span_note(multi_span, "closures can only be coerced to `fn` types if they do not capture any variables"); | |
325 | } | |
326 | } | |
327 | } | |
328 | ||
29967ef6 XL |
329 | /// When encountering an `impl Future` where `BoxFuture` is expected, suggest `Box::pin`. |
330 | pub(in super::super) fn suggest_calling_boxed_future_when_appropriate( | |
331 | &self, | |
332 | err: &mut DiagnosticBuilder<'_>, | |
333 | expr: &hir::Expr<'_>, | |
334 | expected: Ty<'tcx>, | |
335 | found: Ty<'tcx>, | |
336 | ) -> bool { | |
337 | // Handle #68197. | |
338 | ||
339 | if self.tcx.hir().is_inside_const_context(expr.hir_id) { | |
340 | // Do not suggest `Box::new` in const context. | |
341 | return false; | |
342 | } | |
343 | let pin_did = self.tcx.lang_items().pin_type(); | |
344 | match expected.kind() { | |
345 | ty::Adt(def, _) if Some(def.did) != pin_did => return false, | |
346 | // This guards the `unwrap` and `mk_box` below. | |
347 | _ if pin_did.is_none() || self.tcx.lang_items().owned_box().is_none() => return false, | |
348 | _ => {} | |
349 | } | |
350 | let boxed_found = self.tcx.mk_box(found); | |
351 | let new_found = self.tcx.mk_lang_item(boxed_found, LangItem::Pin).unwrap(); | |
352 | if let (true, Ok(snippet)) = ( | |
353 | self.can_coerce(new_found, expected), | |
354 | self.sess().source_map().span_to_snippet(expr.span), | |
355 | ) { | |
356 | match found.kind() { | |
357 | ty::Adt(def, _) if def.is_box() => { | |
358 | err.help("use `Box::pin`"); | |
359 | } | |
360 | _ => { | |
361 | err.span_suggestion( | |
362 | expr.span, | |
363 | "you need to pin and box this expression", | |
364 | format!("Box::pin({})", snippet), | |
365 | Applicability::MachineApplicable, | |
366 | ); | |
367 | } | |
368 | } | |
369 | true | |
370 | } else { | |
371 | false | |
372 | } | |
373 | } | |
374 | ||
375 | /// A common error is to forget to add a semicolon at the end of a block, e.g., | |
376 | /// | |
377 | /// ``` | |
378 | /// fn foo() { | |
379 | /// bar_that_returns_u32() | |
380 | /// } | |
381 | /// ``` | |
382 | /// | |
383 | /// This routine checks if the return expression in a block would make sense on its own as a | |
384 | /// statement and the return type has been left as default or has been specified as `()`. If so, | |
385 | /// it suggests adding a semicolon. | |
386 | fn suggest_missing_semicolon( | |
387 | &self, | |
388 | err: &mut DiagnosticBuilder<'_>, | |
389 | expression: &'tcx hir::Expr<'tcx>, | |
390 | expected: Ty<'tcx>, | |
391 | cause_span: Span, | |
392 | ) { | |
393 | if expected.is_unit() { | |
394 | // `BlockTailExpression` only relevant if the tail expr would be | |
395 | // useful on its own. | |
396 | match expression.kind { | |
397 | ExprKind::Call(..) | |
398 | | ExprKind::MethodCall(..) | |
399 | | ExprKind::Loop(..) | |
5869c6ff | 400 | | ExprKind::If(..) |
29967ef6 | 401 | | ExprKind::Match(..) |
6a06907d XL |
402 | | ExprKind::Block(..) |
403 | if expression.can_have_side_effects() => | |
404 | { | |
29967ef6 XL |
405 | err.span_suggestion( |
406 | cause_span.shrink_to_hi(), | |
6a06907d | 407 | "consider using a semicolon here", |
29967ef6 XL |
408 | ";".to_string(), |
409 | Applicability::MachineApplicable, | |
410 | ); | |
411 | } | |
412 | _ => (), | |
413 | } | |
414 | } | |
415 | } | |
416 | ||
417 | /// A possible error is to forget to add a return type that is needed: | |
418 | /// | |
419 | /// ``` | |
420 | /// fn foo() { | |
421 | /// bar_that_returns_u32() | |
422 | /// } | |
423 | /// ``` | |
424 | /// | |
425 | /// This routine checks if the return type is left as default, the method is not part of an | |
426 | /// `impl` block and that it isn't the `main` method. If so, it suggests setting the return | |
427 | /// type. | |
428 | pub(in super::super) fn suggest_missing_return_type( | |
429 | &self, | |
430 | err: &mut DiagnosticBuilder<'_>, | |
431 | fn_decl: &hir::FnDecl<'_>, | |
432 | expected: Ty<'tcx>, | |
433 | found: Ty<'tcx>, | |
434 | can_suggest: bool, | |
435 | ) -> bool { | |
436 | // Only suggest changing the return type for methods that | |
437 | // haven't set a return type at all (and aren't `fn main()` or an impl). | |
438 | match (&fn_decl.output, found.is_suggestable(), can_suggest, expected.is_unit()) { | |
439 | (&hir::FnRetTy::DefaultReturn(span), true, true, true) => { | |
440 | err.span_suggestion( | |
441 | span, | |
442 | "try adding a return type", | |
443 | format!("-> {} ", self.resolve_vars_with_obligations(found)), | |
444 | Applicability::MachineApplicable, | |
445 | ); | |
446 | true | |
447 | } | |
448 | (&hir::FnRetTy::DefaultReturn(span), false, true, true) => { | |
449 | err.span_label(span, "possibly return type missing here?"); | |
450 | true | |
451 | } | |
452 | (&hir::FnRetTy::DefaultReturn(span), _, false, true) => { | |
453 | // `fn main()` must return `()`, do not suggest changing return type | |
454 | err.span_label(span, "expected `()` because of default return type"); | |
455 | true | |
456 | } | |
457 | // expectation was caused by something else, not the default return | |
458 | (&hir::FnRetTy::DefaultReturn(_), _, _, false) => false, | |
459 | (&hir::FnRetTy::Return(ref ty), _, _, _) => { | |
460 | // Only point to return type if the expected type is the return type, as if they | |
461 | // are not, the expectation must have been caused by something else. | |
462 | debug!("suggest_missing_return_type: return type {:?} node {:?}", ty, ty.kind); | |
463 | let sp = ty.span; | |
6a06907d | 464 | let ty = <dyn AstConv<'_>>::ast_ty_to_ty(self, ty); |
29967ef6 XL |
465 | debug!("suggest_missing_return_type: return type {:?}", ty); |
466 | debug!("suggest_missing_return_type: expected type {:?}", ty); | |
467 | if ty.kind() == expected.kind() { | |
468 | err.span_label(sp, format!("expected `{}` because of return type", expected)); | |
469 | return true; | |
470 | } | |
471 | false | |
472 | } | |
473 | } | |
474 | } | |
475 | ||
6a06907d XL |
476 | pub(in super::super) fn suggest_missing_return_expr( |
477 | &self, | |
478 | err: &mut DiagnosticBuilder<'_>, | |
479 | expr: &'tcx hir::Expr<'tcx>, | |
480 | fn_decl: &hir::FnDecl<'_>, | |
481 | expected: Ty<'tcx>, | |
482 | found: Ty<'tcx>, | |
483 | ) { | |
484 | if !expected.is_unit() { | |
485 | return; | |
486 | } | |
487 | let found = self.resolve_vars_with_obligations(found); | |
488 | if let hir::FnRetTy::Return(ty) = fn_decl.output { | |
489 | let ty = <dyn AstConv<'_>>::ast_ty_to_ty(self, ty); | |
490 | let ty = self.tcx.erase_late_bound_regions(Binder::bind(ty)); | |
491 | let ty = self.normalize_associated_types_in(expr.span, ty); | |
492 | if self.can_coerce(found, ty) { | |
493 | err.multipart_suggestion( | |
494 | "you might have meant to return this value", | |
495 | vec![ | |
496 | (expr.span.shrink_to_lo(), "return ".to_string()), | |
497 | (expr.span.shrink_to_hi(), ";".to_string()), | |
498 | ], | |
499 | Applicability::MaybeIncorrect, | |
500 | ); | |
501 | } | |
502 | } | |
503 | } | |
504 | ||
29967ef6 XL |
505 | pub(in super::super) fn suggest_missing_parentheses( |
506 | &self, | |
507 | err: &mut DiagnosticBuilder<'_>, | |
508 | expr: &hir::Expr<'_>, | |
509 | ) { | |
510 | let sp = self.tcx.sess.source_map().start_point(expr.span); | |
511 | if let Some(sp) = self.tcx.sess.parse_sess.ambiguous_block_expr_parse.borrow().get(&sp) { | |
512 | // `{ 42 } &&x` (#61475) or `{ 42 } && if x { 1 } else { 0 }` | |
513 | self.tcx.sess.parse_sess.expr_parentheses_needed(err, *sp, None); | |
514 | } | |
515 | } | |
516 | } |