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