|
1 | 1 | use crate::FnCtxt;
|
2 | 2 | use rustc_ast::util::parser::PREC_POSTFIX;
|
| 3 | +use rustc_errors::MultiSpan; |
3 | 4 | use rustc_errors::{Applicability, Diagnostic, DiagnosticBuilder, ErrorGuaranteed};
|
4 | 5 | use rustc_hir as hir;
|
5 | 6 | use rustc_hir::def::CtorKind;
|
@@ -30,12 +31,14 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> {
|
30 | 31 | expr_ty: Ty<'tcx>,
|
31 | 32 | expected: Ty<'tcx>,
|
32 | 33 | expected_ty_expr: Option<&'tcx hir::Expr<'tcx>>,
|
33 |
| - _error: Option<TypeError<'tcx>>, |
| 34 | + error: Option<TypeError<'tcx>>, |
34 | 35 | ) {
|
35 | 36 | if expr_ty == expected {
|
36 | 37 | return;
|
37 | 38 | }
|
38 | 39 |
|
| 40 | + self.annotate_alternative_method_deref(err, expr, error); |
| 41 | + |
39 | 42 | // Use `||` to give these suggestions a precedence
|
40 | 43 | let _ = self.suggest_missing_parentheses(err, expr)
|
41 | 44 | || self.suggest_remove_last_method_call(err, expr, expected)
|
@@ -316,6 +319,162 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> {
|
316 | 319 | }
|
317 | 320 | }
|
318 | 321 |
|
| 322 | + fn annotate_alternative_method_deref( |
| 323 | + &self, |
| 324 | + err: &mut Diagnostic, |
| 325 | + expr: &hir::Expr<'_>, |
| 326 | + error: Option<TypeError<'tcx>>, |
| 327 | + ) { |
| 328 | + let parent = self.tcx.hir().get_parent_node(expr.hir_id); |
| 329 | + let Some(TypeError::Sorts(ExpectedFound { expected, .. })) = error else {return;}; |
| 330 | + let Some(hir::Node::Expr(hir::Expr { |
| 331 | + kind: hir::ExprKind::Assign(lhs, rhs, _), .. |
| 332 | + })) = self.tcx.hir().find(parent) else {return; }; |
| 333 | + if rhs.hir_id != expr.hir_id || expected.is_closure() { |
| 334 | + return; |
| 335 | + } |
| 336 | + let hir::ExprKind::Unary(hir::UnOp::Deref, deref) = lhs.kind else { return; }; |
| 337 | + let hir::ExprKind::MethodCall(path, base, args, _) = deref.kind else { return; }; |
| 338 | + let Some(self_ty) = self.typeck_results.borrow().expr_ty_adjusted_opt(base) else { return; }; |
| 339 | + |
| 340 | + let Ok(pick) = self |
| 341 | + .probe_for_name( |
| 342 | + probe::Mode::MethodCall, |
| 343 | + path.ident, |
| 344 | + probe::IsSuggestion(true), |
| 345 | + self_ty, |
| 346 | + deref.hir_id, |
| 347 | + probe::ProbeScope::TraitsInScope, |
| 348 | + ) else { |
| 349 | + return; |
| 350 | + }; |
| 351 | + let in_scope_methods = self.probe_for_name_many( |
| 352 | + probe::Mode::MethodCall, |
| 353 | + path.ident, |
| 354 | + probe::IsSuggestion(true), |
| 355 | + self_ty, |
| 356 | + deref.hir_id, |
| 357 | + probe::ProbeScope::TraitsInScope, |
| 358 | + ); |
| 359 | + let other_methods_in_scope: Vec<_> = |
| 360 | + in_scope_methods.iter().filter(|c| c.item.def_id != pick.item.def_id).collect(); |
| 361 | + |
| 362 | + let all_methods = self.probe_for_name_many( |
| 363 | + probe::Mode::MethodCall, |
| 364 | + path.ident, |
| 365 | + probe::IsSuggestion(true), |
| 366 | + self_ty, |
| 367 | + deref.hir_id, |
| 368 | + probe::ProbeScope::AllTraits, |
| 369 | + ); |
| 370 | + let suggestions: Vec<_> = all_methods |
| 371 | + .into_iter() |
| 372 | + .filter(|c| c.item.def_id != pick.item.def_id) |
| 373 | + .map(|c| { |
| 374 | + let m = c.item; |
| 375 | + let substs = ty::InternalSubsts::for_item(self.tcx, m.def_id, |param, _| { |
| 376 | + self.var_for_def(deref.span, param) |
| 377 | + }); |
| 378 | + vec![ |
| 379 | + ( |
| 380 | + deref.span.until(base.span), |
| 381 | + format!( |
| 382 | + "{}({}", |
| 383 | + with_no_trimmed_paths!( |
| 384 | + self.tcx.def_path_str_with_substs(m.def_id, substs,) |
| 385 | + ), |
| 386 | + match self.tcx.fn_sig(m.def_id).input(0).skip_binder().kind() { |
| 387 | + ty::Ref(_, _, hir::Mutability::Mut) => "&mut ", |
| 388 | + ty::Ref(_, _, _) => "&", |
| 389 | + _ => "", |
| 390 | + }, |
| 391 | + ), |
| 392 | + ), |
| 393 | + match &args[..] { |
| 394 | + [] => (base.span.shrink_to_hi().with_hi(deref.span.hi()), ")".to_string()), |
| 395 | + [first, ..] => (base.span.between(first.span), ", ".to_string()), |
| 396 | + }, |
| 397 | + ] |
| 398 | + }) |
| 399 | + .collect(); |
| 400 | + if suggestions.is_empty() { |
| 401 | + return; |
| 402 | + } |
| 403 | + let mut path_span: MultiSpan = path.ident.span.into(); |
| 404 | + path_span.push_span_label( |
| 405 | + path.ident.span, |
| 406 | + with_no_trimmed_paths!(format!( |
| 407 | + "refers to `{}`", |
| 408 | + self.tcx.def_path_str(pick.item.def_id), |
| 409 | + )), |
| 410 | + ); |
| 411 | + let container_id = pick.item.container_id(self.tcx); |
| 412 | + let container = with_no_trimmed_paths!(self.tcx.def_path_str(container_id)); |
| 413 | + for def_id in pick.import_ids { |
| 414 | + let hir_id = self.tcx.hir().local_def_id_to_hir_id(def_id); |
| 415 | + path_span.push_span_label( |
| 416 | + self.tcx.hir().span(hir_id), |
| 417 | + format!("`{container}` imported here"), |
| 418 | + ); |
| 419 | + } |
| 420 | + let tail = with_no_trimmed_paths!(match &other_methods_in_scope[..] { |
| 421 | + [] => return, |
| 422 | + [candidate] => format!( |
| 423 | + "the method of the same name on {} `{}`", |
| 424 | + match candidate.kind { |
| 425 | + probe::CandidateKind::InherentImplCandidate(..) => "the inherent impl for", |
| 426 | + _ => "trait", |
| 427 | + }, |
| 428 | + self.tcx.def_path_str(candidate.item.container_id(self.tcx)) |
| 429 | + ), |
| 430 | + [.., last] if other_methods_in_scope.len() < 5 => { |
| 431 | + format!( |
| 432 | + "the methods of the same name on {} and `{}`", |
| 433 | + other_methods_in_scope[..other_methods_in_scope.len() - 1] |
| 434 | + .iter() |
| 435 | + .map(|c| format!( |
| 436 | + "`{}`", |
| 437 | + self.tcx.def_path_str(c.item.container_id(self.tcx)) |
| 438 | + )) |
| 439 | + .collect::<Vec<String>>() |
| 440 | + .join(", "), |
| 441 | + self.tcx.def_path_str(last.item.container_id(self.tcx)) |
| 442 | + ) |
| 443 | + } |
| 444 | + _ => format!( |
| 445 | + "the methods of the same name on {} other traits", |
| 446 | + other_methods_in_scope.len() |
| 447 | + ), |
| 448 | + }); |
| 449 | + err.span_note( |
| 450 | + path_span, |
| 451 | + &format!( |
| 452 | + "the `{}` call is resolved to the method in `{container}`, shadowing {tail}", |
| 453 | + path.ident, |
| 454 | + ), |
| 455 | + ); |
| 456 | + if suggestions.len() > other_methods_in_scope.len() { |
| 457 | + err.note(&format!( |
| 458 | + "additionally, there are {} other available methods that aren't in scope", |
| 459 | + suggestions.len() - other_methods_in_scope.len() |
| 460 | + )); |
| 461 | + } |
| 462 | + err.multipart_suggestions( |
| 463 | + &format!( |
| 464 | + "you might have meant to call {}; you can use the fully-qualified path to call {} \ |
| 465 | + explicitly", |
| 466 | + if suggestions.len() == 1 { |
| 467 | + "the other method" |
| 468 | + } else { |
| 469 | + "one of the other methods" |
| 470 | + }, |
| 471 | + if suggestions.len() == 1 { "it" } else { "one of them" }, |
| 472 | + ), |
| 473 | + suggestions, |
| 474 | + Applicability::MaybeIncorrect, |
| 475 | + ); |
| 476 | + } |
| 477 | + |
319 | 478 | /// If the expected type is an enum (Issue #55250) with any variants whose
|
320 | 479 | /// sole field is of the found type, suggest such variants. (Issue #42764)
|
321 | 480 | fn suggest_compatible_variants(
|
|
0 commit comments