|
1 | 1 | #![allow(rustc::diagnostic_outside_of_impl)]
|
2 | 2 | #![allow(rustc::untranslatable_diagnostic)]
|
3 | 3 |
|
| 4 | +use rustc_data_structures::fx::FxHashSet; |
4 | 5 | use rustc_errors::{Applicability, Diag};
|
5 | 6 | use rustc_hir::intravisit::Visitor;
|
6 |
| -use rustc_hir::{CaptureBy, ExprKind, HirId, Node}; |
| 7 | +use rustc_hir::{self as hir, CaptureBy, ExprKind, HirId, Node}; |
7 | 8 | use rustc_middle::bug;
|
8 | 9 | use rustc_middle::mir::*;
|
9 | 10 | use rustc_middle::ty::{self, Ty};
|
@@ -683,48 +684,126 @@ impl<'infcx, 'tcx> MirBorrowckCtxt<'_, 'infcx, 'tcx> {
|
683 | 684 | }
|
684 | 685 |
|
685 | 686 | fn add_move_error_suggestions(&self, err: &mut Diag<'_>, binds_to: &[Local]) {
|
686 |
| - let mut suggestions: Vec<(Span, String, String)> = Vec::new(); |
| 687 | + /// A HIR visitor to associate each binding with a `&` or `&mut` that could be removed to |
| 688 | + /// make it bind by reference instead (if possible) |
| 689 | + struct BindingFinder<'tcx> { |
| 690 | + typeck_results: &'tcx ty::TypeckResults<'tcx>, |
| 691 | + hir: rustc_middle::hir::map::Map<'tcx>, |
| 692 | + /// Input: the span of the pattern we're finding bindings in |
| 693 | + pat_span: Span, |
| 694 | + /// Input: the spans of the bindings we're providing suggestions for |
| 695 | + binding_spans: Vec<Span>, |
| 696 | + /// Internal state: have we reached the pattern we're finding bindings in? |
| 697 | + found_pat: bool, |
| 698 | + /// Internal state: the innermost `&` or `&mut` "above" the visitor |
| 699 | + ref_pat: Option<&'tcx hir::Pat<'tcx>>, |
| 700 | + /// Internal state: could removing a `&` give bindings unexpected types? |
| 701 | + has_adjustments: bool, |
| 702 | + /// Output: for each input binding, the `&` or `&mut` to remove to make it by-ref |
| 703 | + ref_pat_for_binding: Vec<(Span, Option<&'tcx hir::Pat<'tcx>>)>, |
| 704 | + /// Output: ref patterns that can't be removed straightforwardly |
| 705 | + cannot_remove: FxHashSet<HirId>, |
| 706 | + } |
| 707 | + impl<'tcx> Visitor<'tcx> for BindingFinder<'tcx> { |
| 708 | + type NestedFilter = rustc_middle::hir::nested_filter::OnlyBodies; |
| 709 | + |
| 710 | + fn nested_visit_map(&mut self) -> Self::Map { |
| 711 | + self.hir |
| 712 | + } |
| 713 | + |
| 714 | + fn visit_expr(&mut self, ex: &'tcx hir::Expr<'tcx>) -> Self::Result { |
| 715 | + // Don't walk into const patterns or anything else that might confuse this |
| 716 | + if !self.found_pat { |
| 717 | + hir::intravisit::walk_expr(self, ex) |
| 718 | + } |
| 719 | + } |
| 720 | + |
| 721 | + fn visit_pat(&mut self, p: &'tcx hir::Pat<'tcx>) { |
| 722 | + if p.span == self.pat_span { |
| 723 | + self.found_pat = true; |
| 724 | + } |
| 725 | + |
| 726 | + let parent_has_adjustments = self.has_adjustments; |
| 727 | + self.has_adjustments |= |
| 728 | + self.typeck_results.pat_adjustments().contains_key(p.hir_id); |
| 729 | + |
| 730 | + // Track the innermost `&` or `&mut` enclosing bindings, to suggest removing it. |
| 731 | + let parent_ref_pat = self.ref_pat; |
| 732 | + if let hir::PatKind::Ref(..) = p.kind { |
| 733 | + self.ref_pat = Some(p); |
| 734 | + // To avoid edition-dependent logic to figure out how many refs this `&` can |
| 735 | + // peel off, simply don't remove the "parent" `&`. |
| 736 | + self.cannot_remove.extend(parent_ref_pat.map(|r| r.hir_id)); |
| 737 | + if self.has_adjustments { |
| 738 | + // Removing this `&` could give child bindings unexpected types, so don't. |
| 739 | + self.cannot_remove.insert(p.hir_id); |
| 740 | + // As long the `&` stays, child patterns' types should be as expected. |
| 741 | + self.has_adjustments = false; |
| 742 | + } |
| 743 | + } |
| 744 | + |
| 745 | + if let hir::PatKind::Binding(_, _, ident, _) = p.kind { |
| 746 | + // the spans in `binding_spans` encompass both the ident and binding mode |
| 747 | + if let Some(&bind_sp) = |
| 748 | + self.binding_spans.iter().find(|bind_sp| bind_sp.contains(ident.span)) |
| 749 | + { |
| 750 | + self.ref_pat_for_binding.push((bind_sp, self.ref_pat)); |
| 751 | + } else { |
| 752 | + // we've encountered a binding that we're not reporting a move error for. |
| 753 | + // we don't want to change its type, so don't remove the surrounding `&`. |
| 754 | + if let Some(ref_pat) = self.ref_pat { |
| 755 | + self.cannot_remove.insert(ref_pat.hir_id); |
| 756 | + } |
| 757 | + } |
| 758 | + } |
| 759 | + |
| 760 | + hir::intravisit::walk_pat(self, p); |
| 761 | + self.ref_pat = parent_ref_pat; |
| 762 | + self.has_adjustments = parent_has_adjustments; |
| 763 | + } |
| 764 | + } |
| 765 | + let mut pat_span = None; |
| 766 | + let mut binding_spans = Vec::new(); |
687 | 767 | for local in binds_to {
|
688 | 768 | let bind_to = &self.body.local_decls[*local];
|
689 |
| - if let LocalInfo::User(BindingForm::Var(VarBindingForm { pat_span, .. })) = |
| 769 | + if let LocalInfo::User(BindingForm::Var(VarBindingForm { pat_span: pat_sp, .. })) = |
690 | 770 | *bind_to.local_info()
|
691 | 771 | {
|
692 |
| - let Ok(pat_snippet) = self.infcx.tcx.sess.source_map().span_to_snippet(pat_span) |
693 |
| - else { |
694 |
| - continue; |
695 |
| - }; |
696 |
| - let Some(stripped) = pat_snippet.strip_prefix('&') else { |
697 |
| - suggestions.push(( |
698 |
| - bind_to.source_info.span.shrink_to_lo(), |
699 |
| - "consider borrowing the pattern binding".to_string(), |
700 |
| - "ref ".to_string(), |
701 |
| - )); |
702 |
| - continue; |
703 |
| - }; |
704 |
| - let inner_pat_snippet = stripped.trim_start(); |
705 |
| - let (pat_span, suggestion, to_remove) = if inner_pat_snippet.starts_with("mut") |
706 |
| - && inner_pat_snippet["mut".len()..].starts_with(rustc_lexer::is_whitespace) |
707 |
| - { |
708 |
| - let inner_pat_snippet = inner_pat_snippet["mut".len()..].trim_start(); |
709 |
| - let pat_span = pat_span.with_hi( |
710 |
| - pat_span.lo() |
711 |
| - + BytePos((pat_snippet.len() - inner_pat_snippet.len()) as u32), |
712 |
| - ); |
713 |
| - (pat_span, String::new(), "mutable borrow") |
714 |
| - } else { |
715 |
| - let pat_span = pat_span.with_hi( |
716 |
| - pat_span.lo() |
717 |
| - + BytePos( |
718 |
| - (pat_snippet.len() - inner_pat_snippet.trim_start().len()) as u32, |
719 |
| - ), |
720 |
| - ); |
721 |
| - (pat_span, String::new(), "borrow") |
722 |
| - }; |
723 |
| - suggestions.push(( |
724 |
| - pat_span, |
725 |
| - format!("consider removing the {to_remove}"), |
726 |
| - suggestion, |
727 |
| - )); |
| 772 | + pat_span = Some(pat_sp); |
| 773 | + binding_spans.push(bind_to.source_info.span); |
| 774 | + } |
| 775 | + } |
| 776 | + let Some(pat_span) = pat_span else { return }; |
| 777 | + |
| 778 | + let hir = self.infcx.tcx.hir(); |
| 779 | + let Some(body) = hir.maybe_body_owned_by(self.mir_def_id()) else { return }; |
| 780 | + let typeck_results = self.infcx.tcx.typeck(self.mir_def_id()); |
| 781 | + let mut finder = BindingFinder { |
| 782 | + typeck_results, |
| 783 | + hir, |
| 784 | + pat_span, |
| 785 | + binding_spans, |
| 786 | + found_pat: false, |
| 787 | + ref_pat: None, |
| 788 | + has_adjustments: false, |
| 789 | + ref_pat_for_binding: Vec::new(), |
| 790 | + cannot_remove: FxHashSet::default(), |
| 791 | + }; |
| 792 | + finder.visit_body(body); |
| 793 | + |
| 794 | + let mut suggestions = Vec::new(); |
| 795 | + for (binding_span, opt_ref_pat) in finder.ref_pat_for_binding { |
| 796 | + if let Some(ref_pat) = opt_ref_pat |
| 797 | + && !finder.cannot_remove.contains(&ref_pat.hir_id) |
| 798 | + && let hir::PatKind::Ref(subpat, mutbl) = ref_pat.kind |
| 799 | + && let Some(ref_span) = ref_pat.span.trim_end(subpat.span) |
| 800 | + { |
| 801 | + let mutable_str = if mutbl.is_mut() { "mutable " } else { "" }; |
| 802 | + let msg = format!("consider removing the {mutable_str}borrow"); |
| 803 | + suggestions.push((ref_span, msg, "".to_string())); |
| 804 | + } else { |
| 805 | + let msg = "consider borrowing the pattern binding".to_string(); |
| 806 | + suggestions.push((binding_span.shrink_to_lo(), msg, "ref ".to_string())); |
728 | 807 | }
|
729 | 808 | }
|
730 | 809 | suggestions.sort_unstable_by_key(|&(span, _, _)| span);
|
|
0 commit comments