about summary refs log tree commit diff
diff options
context:
space:
mode:
authorbors <bors@rust-lang.org>2024-11-14 17:22:10 +0000
committerbors <bors@rust-lang.org>2024-11-14 17:22:10 +0000
commit90ab8eaedd3efd7919bffa7638db66e67df3f0b8 (patch)
tree4badabdee1af67ff905ba81319d83b98d7ebb830
parentc82e0dff84f922274c3060f09b5ae78d5dbf5c49 (diff)
parent35214ebf32e2383661e280f4028803e812f2c2ad (diff)
downloadrust-90ab8eaedd3efd7919bffa7638db66e67df3f0b8.tar.gz
rust-90ab8eaedd3efd7919bffa7638db66e67df3f0b8.zip
Auto merge of #133039 - GuillaumeGomez:rollup-i223onq, r=GuillaumeGomez
Rollup of 5 pull requests

Successful merges:

 - #132172 (borrowck diagnostics: suggest borrowing function inputs in generic positions)
 - #132649 (add ./x clippy ci)
 - #133005 (rustdoc: use a trie for name-based search)
 - #133034 (update download-rustc comments and default)
 - #133036 (add myself into `users_on_vacation` on triagebot)

r? `@ghost`
`@rustbot` modify labels: rollup
-rw-r--r--compiler/rustc_borrowck/src/diagnostics/conflict_errors.rs368
-rw-r--r--config.example.toml4
-rw-r--r--src/bootstrap/src/core/build_steps/clean.rs2
-rw-r--r--src/bootstrap/src/core/build_steps/clippy.rs128
-rw-r--r--src/bootstrap/src/core/builder/mod.rs1
-rw-r--r--src/bootstrap/src/core/config/tests.rs12
-rw-r--r--src/bootstrap/src/utils/helpers.rs2
-rw-r--r--src/ci/docker/host-x86_64/mingw-check/Dockerfile4
-rw-r--r--src/librustdoc/html/static/js/search.js815
-rw-r--r--tests/rustdoc-js-std/deduplication.js1
-rw-r--r--tests/rustdoc-js-std/path-maxeditdistance.js12
-rw-r--r--tests/rustdoc-js/non-english-identifier.js26
-rw-r--r--tests/rustdoc-js/path-maxeditdistance.js25
-rw-r--r--tests/rustdoc-js/prototype.js4
-rw-r--r--tests/ui/closures/2229_closure_analysis/diagnostics/closure-origin-multi-variant-diagnostics.stderr4
-rw-r--r--tests/ui/closures/2229_closure_analysis/diagnostics/closure-origin-single-variant-diagnostics.stderr4
-rw-r--r--tests/ui/closures/2229_closure_analysis/diagnostics/closure-origin-struct-diagnostics.stderr4
-rw-r--r--tests/ui/closures/2229_closure_analysis/diagnostics/closure-origin-tuple-diagnostics-1.stderr4
-rw-r--r--tests/ui/moves/auxiliary/suggest-borrow-for-generic-arg-aux.rs20
-rw-r--r--tests/ui/moves/borrow-closures-instead-of-move.rs2
-rw-r--r--tests/ui/moves/borrow-closures-instead-of-move.stderr22
-rw-r--r--tests/ui/moves/moved-value-on-as-ref-arg.stderr8
-rw-r--r--tests/ui/moves/moves-based-on-type-no-recursive-stack-closure.stderr4
-rw-r--r--tests/ui/moves/suggest-borrow-for-generic-arg.fixed46
-rw-r--r--tests/ui/moves/suggest-borrow-for-generic-arg.rs46
-rw-r--r--tests/ui/moves/suggest-borrow-for-generic-arg.stderr93
-rw-r--r--tests/ui/not-copy-closure.stderr4
-rw-r--r--triagebot.toml1
28 files changed, 1275 insertions, 391 deletions
diff --git a/compiler/rustc_borrowck/src/diagnostics/conflict_errors.rs b/compiler/rustc_borrowck/src/diagnostics/conflict_errors.rs
index e5b28289faa..454fd14ea74 100644
--- a/compiler/rustc_borrowck/src/diagnostics/conflict_errors.rs
+++ b/compiler/rustc_borrowck/src/diagnostics/conflict_errors.rs
@@ -27,7 +27,7 @@ use rustc_middle::mir::{
 };
 use rustc_middle::ty::print::PrintTraitRefExt as _;
 use rustc_middle::ty::{
-    self, PredicateKind, Ty, TyCtxt, TypeSuperVisitable, TypeVisitor, Upcast,
+    self, ClauseKind, PredicateKind, Ty, TyCtxt, TypeSuperVisitable, TypeVisitor, Upcast,
     suggest_constraining_type_params,
 };
 use rustc_middle::util::CallKind;
@@ -39,6 +39,7 @@ use rustc_span::{BytePos, Span, Symbol};
 use rustc_trait_selection::error_reporting::InferCtxtErrorExt;
 use rustc_trait_selection::error_reporting::traits::FindExprBySpan;
 use rustc_trait_selection::infer::InferCtxtExt;
+use rustc_trait_selection::traits::query::evaluate_obligation::InferCtxtExt as _;
 use rustc_trait_selection::traits::{Obligation, ObligationCause, ObligationCtxt};
 use tracing::{debug, instrument};
 
@@ -201,16 +202,15 @@ impl<'infcx, 'tcx> MirBorrowckCtxt<'_, 'infcx, 'tcx> {
 
                 let mut has_suggest_reborrow = false;
                 if !seen_spans.contains(&move_span) {
-                    if !closure {
-                        self.suggest_ref_or_clone(
-                            mpi,
-                            &mut err,
-                            &mut in_pattern,
-                            move_spans,
-                            moved_place.as_ref(),
-                            &mut has_suggest_reborrow,
-                        );
-                    }
+                    self.suggest_ref_or_clone(
+                        mpi,
+                        &mut err,
+                        &mut in_pattern,
+                        move_spans,
+                        moved_place.as_ref(),
+                        &mut has_suggest_reborrow,
+                        closure,
+                    );
 
                     let msg_opt = CapturedMessageOpt {
                         is_partial_move,
@@ -266,27 +266,11 @@ impl<'infcx, 'tcx> MirBorrowckCtxt<'_, 'infcx, 'tcx> {
                 }
             }
 
-            let opt_name = self.describe_place_with_options(place.as_ref(), DescribePlaceOpt {
-                including_downcast: true,
-                including_tuple_field: true,
-            });
-            let note_msg = match opt_name {
-                Some(name) => format!("`{name}`"),
-                None => "value".to_owned(),
-            };
-            if self.suggest_borrow_fn_like(&mut err, ty, &move_site_vec, &note_msg)
-                || if let UseSpans::FnSelfUse { kind, .. } = use_spans
-                    && let CallKind::FnCall { fn_trait_id, self_ty } = kind
-                    && let ty::Param(_) = self_ty.kind()
-                    && ty == self_ty
-                    && self.infcx.tcx.is_lang_item(fn_trait_id, LangItem::FnOnce)
-                {
-                    // this is a type parameter `T: FnOnce()`, don't suggest `T: FnOnce() + Clone`.
-                    true
-                } else {
-                    false
-                }
-            {
+            if self.param_env.caller_bounds().iter().any(|c| {
+                c.as_trait_clause().is_some_and(|pred| {
+                    pred.skip_binder().self_ty() == ty && self.infcx.tcx.is_fn_trait(pred.def_id())
+                })
+            }) {
                 // Suppress the next suggestion since we don't want to put more bounds onto
                 // something that already has `Fn`-like bounds (or is a closure), so we can't
                 // restrict anyways.
@@ -295,6 +279,14 @@ impl<'infcx, 'tcx> MirBorrowckCtxt<'_, 'infcx, 'tcx> {
                 self.suggest_adding_bounds(&mut err, ty, copy_did, span);
             }
 
+            let opt_name = self.describe_place_with_options(place.as_ref(), DescribePlaceOpt {
+                including_downcast: true,
+                including_tuple_field: true,
+            });
+            let note_msg = match opt_name {
+                Some(name) => format!("`{name}`"),
+                None => "value".to_owned(),
+            };
             if needs_note {
                 if let Some(local) = place.as_local() {
                     let span = self.body.local_decls[local].source_info.span;
@@ -341,6 +333,7 @@ impl<'infcx, 'tcx> MirBorrowckCtxt<'_, 'infcx, 'tcx> {
         move_spans: UseSpans<'tcx>,
         moved_place: PlaceRef<'tcx>,
         has_suggest_reborrow: &mut bool,
+        moved_or_invoked_closure: bool,
     ) {
         let move_span = match move_spans {
             UseSpans::ClosureUse { capture_kind_span, .. } => capture_kind_span,
@@ -428,104 +421,76 @@ impl<'infcx, 'tcx> MirBorrowckCtxt<'_, 'infcx, 'tcx> {
                 }
                 let typeck = self.infcx.tcx.typeck(self.mir_def_id());
                 let parent = self.infcx.tcx.parent_hir_node(expr.hir_id);
-                let (def_id, args, offset) = if let hir::Node::Expr(parent_expr) = parent
+                let (def_id, call_id, args, offset) = if let hir::Node::Expr(parent_expr) = parent
                     && let hir::ExprKind::MethodCall(_, _, args, _) = parent_expr.kind
                 {
-                    (typeck.type_dependent_def_id(parent_expr.hir_id), args, 1)
+                    let def_id = typeck.type_dependent_def_id(parent_expr.hir_id);
+                    (def_id, Some(parent_expr.hir_id), args, 1)
                 } else if let hir::Node::Expr(parent_expr) = parent
                     && let hir::ExprKind::Call(call, args) = parent_expr.kind
                     && let ty::FnDef(def_id, _) = typeck.node_type(call.hir_id).kind()
                 {
-                    (Some(*def_id), args, 0)
+                    (Some(*def_id), Some(call.hir_id), args, 0)
                 } else {
-                    (None, &[][..], 0)
+                    (None, None, &[][..], 0)
                 };
+                let ty = place.ty(self.body, self.infcx.tcx).ty;
 
-                // If the moved value is a mut reference, it is used in a
-                // generic function and it's type is a generic param, it can be
-                // reborrowed to avoid moving.
-                // for example:
-                // struct Y(u32);
-                // x's type is '& mut Y' and it is used in `fn generic<T>(x: T) {}`.
+                let mut can_suggest_clone = true;
                 if let Some(def_id) = def_id
-                    && self.infcx.tcx.def_kind(def_id).is_fn_like()
                     && let Some(pos) = args.iter().position(|arg| arg.hir_id == expr.hir_id)
-                    && let Some(arg) = self
-                        .infcx
-                        .tcx
-                        .fn_sig(def_id)
-                        .skip_binder()
-                        .skip_binder()
-                        .inputs()
-                        .get(pos + offset)
-                    && let ty::Param(_) = arg.kind()
                 {
-                    let place = &self.move_data.move_paths[mpi].place;
-                    let ty = place.ty(self.body, self.infcx.tcx).ty;
-                    if let ty::Ref(_, _, hir::Mutability::Mut) = ty.kind() {
+                    // The move occurred as one of the arguments to a function call. Is that
+                    // argument generic? `def_id` can't be a closure here, so using `fn_sig` is fine
+                    let arg_param = if self.infcx.tcx.def_kind(def_id).is_fn_like()
+                        && let sig =
+                            self.infcx.tcx.fn_sig(def_id).instantiate_identity().skip_binder()
+                        && let Some(arg_ty) = sig.inputs().get(pos + offset)
+                        && let ty::Param(arg_param) = arg_ty.kind()
+                    {
+                        Some(arg_param)
+                    } else {
+                        None
+                    };
+
+                    // If the moved value is a mut reference, it is used in a
+                    // generic function and it's type is a generic param, it can be
+                    // reborrowed to avoid moving.
+                    // for example:
+                    // struct Y(u32);
+                    // x's type is '& mut Y' and it is used in `fn generic<T>(x: T) {}`.
+                    if let ty::Ref(_, _, hir::Mutability::Mut) = ty.kind()
+                        && arg_param.is_some()
+                    {
                         *has_suggest_reborrow = true;
                         self.suggest_reborrow(err, expr.span, moved_place);
                         return;
                     }
-                }
 
-                let mut can_suggest_clone = true;
-                if let Some(def_id) = def_id
-                    && let Some(local_def_id) = def_id.as_local()
-                    && let node = self.infcx.tcx.hir_node_by_def_id(local_def_id)
-                    && let Some(fn_sig) = node.fn_sig()
-                    && let Some(ident) = node.ident()
-                    && let Some(pos) = args.iter().position(|arg| arg.hir_id == expr.hir_id)
-                    && let Some(arg) = fn_sig.decl.inputs.get(pos + offset)
-                {
-                    let mut is_mut = false;
-                    if let hir::TyKind::Path(hir::QPath::Resolved(None, path)) = arg.kind
-                        && let Res::Def(DefKind::TyParam, param_def_id) = path.res
-                        && self
-                            .infcx
-                            .tcx
-                            .predicates_of(def_id)
-                            .instantiate_identity(self.infcx.tcx)
-                            .predicates
-                            .into_iter()
-                            .any(|pred| {
-                                if let ty::ClauseKind::Trait(predicate) = pred.kind().skip_binder()
-                                    && [
-                                        self.infcx.tcx.get_diagnostic_item(sym::AsRef),
-                                        self.infcx.tcx.get_diagnostic_item(sym::AsMut),
-                                        self.infcx.tcx.get_diagnostic_item(sym::Borrow),
-                                        self.infcx.tcx.get_diagnostic_item(sym::BorrowMut),
-                                    ]
-                                    .contains(&Some(predicate.def_id()))
-                                    && let ty::Param(param) = predicate.self_ty().kind()
-                                    && let generics = self.infcx.tcx.generics_of(def_id)
-                                    && let param = generics.type_param(*param, self.infcx.tcx)
-                                    && param.def_id == param_def_id
-                                {
-                                    if [
-                                        self.infcx.tcx.get_diagnostic_item(sym::AsMut),
-                                        self.infcx.tcx.get_diagnostic_item(sym::BorrowMut),
-                                    ]
-                                    .contains(&Some(predicate.def_id()))
-                                    {
-                                        is_mut = true;
-                                    }
-                                    true
-                                } else {
-                                    false
-                                }
-                            })
+                    // If the moved place is used generically by the callee and a reference to it
+                    // would still satisfy any bounds on its type, suggest borrowing.
+                    if let Some(&param) = arg_param
+                        && let Some(generic_args) = call_id.and_then(|id| typeck.node_args_opt(id))
+                        && let Some(ref_mutability) = self.suggest_borrow_generic_arg(
+                            err,
+                            def_id,
+                            generic_args,
+                            param,
+                            moved_place,
+                            pos + offset,
+                            ty,
+                            expr.span,
+                        )
                     {
-                        // The type of the argument corresponding to the expression that got moved
-                        // is a type parameter `T`, which is has a `T: AsRef` obligation.
-                        err.span_suggestion_verbose(
-                            expr.span.shrink_to_lo(),
-                            "borrow the value to avoid moving it",
-                            format!("&{}", if is_mut { "mut " } else { "" }),
-                            Applicability::MachineApplicable,
-                        );
-                        can_suggest_clone = is_mut;
-                    } else {
+                        can_suggest_clone = ref_mutability.is_mut();
+                    } else if let Some(local_def_id) = def_id.as_local()
+                        && let node = self.infcx.tcx.hir_node_by_def_id(local_def_id)
+                        && let Some(fn_decl) = node.fn_decl()
+                        && let Some(ident) = node.ident()
+                        && let Some(arg) = fn_decl.inputs.get(pos + offset)
+                    {
+                        // If we can't suggest borrowing in the call, but the function definition
+                        // is local, instead offer changing the function to borrow that argument.
                         let mut span: MultiSpan = arg.span.into();
                         span.push_span_label(
                             arg.span,
@@ -546,8 +511,6 @@ impl<'infcx, 'tcx> MirBorrowckCtxt<'_, 'infcx, 'tcx> {
                         );
                     }
                 }
-                let place = &self.move_data.move_paths[mpi].place;
-                let ty = place.ty(self.body, self.infcx.tcx).ty;
                 if let hir::Node::Expr(parent_expr) = parent
                     && let hir::ExprKind::Call(call_expr, _) = parent_expr.kind
                     && let hir::ExprKind::Path(hir::QPath::LangItem(LangItem::IntoIterIntoIter, _)) =
@@ -557,6 +520,8 @@ impl<'infcx, 'tcx> MirBorrowckCtxt<'_, 'infcx, 'tcx> {
                 } else if let UseSpans::FnSelfUse { kind: CallKind::Normal { .. }, .. } = move_spans
                 {
                     // We already suggest cloning for these cases in `explain_captures`.
+                } else if moved_or_invoked_closure {
+                    // Do not suggest `closure.clone()()`.
                 } else if let UseSpans::ClosureUse {
                     closure_kind:
                         ClosureKind::Coroutine(CoroutineKind::Desugared(_, CoroutineSource::Block)),
@@ -665,6 +630,113 @@ impl<'infcx, 'tcx> MirBorrowckCtxt<'_, 'infcx, 'tcx> {
         );
     }
 
+    /// If a place is used after being moved as an argument to a function, the function is generic
+    /// in that argument, and a reference to the argument's type would still satisfy the function's
+    /// bounds, suggest borrowing. This covers, e.g., borrowing an `impl Fn()` argument being passed
+    /// in an `impl FnOnce()` position.
+    /// Returns `Some(mutability)` when suggesting to borrow with mutability `mutability`, or `None`
+    /// if no suggestion is made.
+    fn suggest_borrow_generic_arg(
+        &self,
+        err: &mut Diag<'_>,
+        callee_did: DefId,
+        generic_args: ty::GenericArgsRef<'tcx>,
+        param: ty::ParamTy,
+        moved_place: PlaceRef<'tcx>,
+        moved_arg_pos: usize,
+        moved_arg_ty: Ty<'tcx>,
+        place_span: Span,
+    ) -> Option<ty::Mutability> {
+        let tcx = self.infcx.tcx;
+        let sig = tcx.fn_sig(callee_did).instantiate_identity().skip_binder();
+        let clauses = tcx.predicates_of(callee_did).instantiate_identity(self.infcx.tcx).predicates;
+
+        // First, is there at least one method on one of `param`'s trait bounds?
+        // This keeps us from suggesting borrowing the argument to `mem::drop`, e.g.
+        if !clauses.iter().any(|clause| {
+            clause.as_trait_clause().is_some_and(|tc| {
+                tc.self_ty().skip_binder().is_param(param.index)
+                    && tc.polarity() == ty::PredicatePolarity::Positive
+                    && tcx
+                        .supertrait_def_ids(tc.def_id())
+                        .flat_map(|trait_did| tcx.associated_items(trait_did).in_definition_order())
+                        .any(|item| item.fn_has_self_parameter)
+            })
+        }) {
+            return None;
+        }
+
+        // Try borrowing a shared reference first, then mutably.
+        if let Some(mutbl) = [ty::Mutability::Not, ty::Mutability::Mut].into_iter().find(|&mutbl| {
+            let re = self.infcx.tcx.lifetimes.re_erased;
+            let ref_ty = Ty::new_ref(self.infcx.tcx, re, moved_arg_ty, mutbl);
+
+            // Ensure that substituting `ref_ty` in the callee's signature doesn't break
+            // other inputs or the return type.
+            let new_args = tcx.mk_args_from_iter(generic_args.iter().enumerate().map(
+                |(i, arg)| {
+                    if i == param.index as usize { ref_ty.into() } else { arg }
+                },
+            ));
+            let can_subst = |ty: Ty<'tcx>| {
+                // Normalize before comparing to see through type aliases and projections.
+                let old_ty = ty::EarlyBinder::bind(ty).instantiate(tcx, generic_args);
+                let new_ty = ty::EarlyBinder::bind(ty).instantiate(tcx, new_args);
+                if let Ok(old_ty) = tcx.try_normalize_erasing_regions(self.param_env, old_ty)
+                    && let Ok(new_ty) = tcx.try_normalize_erasing_regions(self.param_env, new_ty)
+                {
+                    old_ty == new_ty
+                } else {
+                    false
+                }
+            };
+            if !can_subst(sig.output())
+                || sig
+                    .inputs()
+                    .iter()
+                    .enumerate()
+                    .any(|(i, &input_ty)| i != moved_arg_pos && !can_subst(input_ty))
+            {
+                return false;
+            }
+
+            // Test the callee's predicates, substituting a reference in for the self ty
+            // in bounds on `param`.
+            clauses.iter().all(|&clause| {
+                let clause_for_ref = clause.kind().map_bound(|kind| match kind {
+                    ClauseKind::Trait(c) if c.self_ty().is_param(param.index) => {
+                        ClauseKind::Trait(c.with_self_ty(tcx, ref_ty))
+                    }
+                    ClauseKind::Projection(c) if c.self_ty().is_param(param.index) => {
+                        ClauseKind::Projection(c.with_self_ty(tcx, ref_ty))
+                    }
+                    _ => kind,
+                });
+                self.infcx.predicate_must_hold_modulo_regions(&Obligation::new(
+                    tcx,
+                    ObligationCause::dummy(),
+                    self.param_env,
+                    ty::EarlyBinder::bind(clause_for_ref).instantiate(tcx, generic_args),
+                ))
+            })
+        }) {
+            let place_desc = if let Some(desc) = self.describe_place(moved_place) {
+                format!("`{desc}`")
+            } else {
+                "here".to_owned()
+            };
+            err.span_suggestion_verbose(
+                place_span.shrink_to_lo(),
+                format!("consider {}borrowing {place_desc}", mutbl.mutably_str()),
+                mutbl.ref_prefix_str(),
+                Applicability::MaybeIncorrect,
+            );
+            Some(mutbl)
+        } else {
+            None
+        }
+    }
+
     fn report_use_of_uninitialized(
         &self,
         mpi: MovePathIndex,
@@ -845,74 +917,6 @@ impl<'infcx, 'tcx> MirBorrowckCtxt<'_, 'infcx, 'tcx> {
         );
     }
 
-    fn suggest_borrow_fn_like(
-        &self,
-        err: &mut Diag<'_>,
-        ty: Ty<'tcx>,
-        move_sites: &[MoveSite],
-        value_name: &str,
-    ) -> bool {
-        let tcx = self.infcx.tcx;
-
-        // Find out if the predicates show that the type is a Fn or FnMut
-        let find_fn_kind_from_did = |(pred, _): (ty::Clause<'tcx>, _)| {
-            if let ty::ClauseKind::Trait(pred) = pred.kind().skip_binder()
-                && pred.self_ty() == ty
-            {
-                if tcx.is_lang_item(pred.def_id(), LangItem::Fn) {
-                    return Some(hir::Mutability::Not);
-                } else if tcx.is_lang_item(pred.def_id(), LangItem::FnMut) {
-                    return Some(hir::Mutability::Mut);
-                }
-            }
-            None
-        };
-
-        // If the type is opaque/param/closure, and it is Fn or FnMut, let's suggest (mutably)
-        // borrowing the type, since `&mut F: FnMut` iff `F: FnMut` and similarly for `Fn`.
-        // These types seem reasonably opaque enough that they could be instantiated with their
-        // borrowed variants in a function body when we see a move error.
-        let borrow_level = match *ty.kind() {
-            ty::Param(_) => tcx
-                .explicit_predicates_of(self.mir_def_id().to_def_id())
-                .predicates
-                .iter()
-                .copied()
-                .find_map(find_fn_kind_from_did),
-            ty::Alias(ty::Opaque, ty::AliasTy { def_id, args, .. }) => tcx
-                .explicit_item_super_predicates(def_id)
-                .iter_instantiated_copied(tcx, args)
-                .find_map(|(clause, span)| find_fn_kind_from_did((clause, span))),
-            ty::Closure(_, args) => match args.as_closure().kind() {
-                ty::ClosureKind::Fn => Some(hir::Mutability::Not),
-                ty::ClosureKind::FnMut => Some(hir::Mutability::Mut),
-                _ => None,
-            },
-            _ => None,
-        };
-
-        let Some(borrow_level) = borrow_level else {
-            return false;
-        };
-        let sugg = move_sites
-            .iter()
-            .map(|move_site| {
-                let move_out = self.move_data.moves[(*move_site).moi];
-                let moved_place = &self.move_data.move_paths[move_out.path].place;
-                let move_spans = self.move_spans(moved_place.as_ref(), move_out.source);
-                let move_span = move_spans.args_or_use();
-                let suggestion = borrow_level.ref_prefix_str().to_owned();
-                (move_span.shrink_to_lo(), suggestion)
-            })
-            .collect();
-        err.multipart_suggestion_verbose(
-            format!("consider {}borrowing {value_name}", borrow_level.mutably_str()),
-            sugg,
-            Applicability::MaybeIncorrect,
-        );
-        true
-    }
-
     /// In a move error that occurs on a call within a loop, we try to identify cases where cloning
     /// the value would lead to a logic error. We infer these cases by seeing if the moved value is
     /// part of the logic to break the loop, either through an explicit `break` or if the expression
diff --git a/config.example.toml b/config.example.toml
index 2bc79a6d6ba..9dc71b10f70 100644
--- a/config.example.toml
+++ b/config.example.toml
@@ -503,8 +503,8 @@
 # Set this to "if-unchanged" if you are working on `src/tools`, `tests` or `library` (on CI, `library`
 # changes triggers in-tree compiler build) to speed up the build process.
 #
-# Set this to `true` to download unconditionally.
-#download-rustc = false
+# Set this to `true` to always download or `false` to always use the in-tree compiler.
+#download-rustc = "if-unchanged"
 
 # Number of codegen units to use for each compiler invocation. A value of 0
 # means "the number of cores on this machine", and 1+ is passed through to the
diff --git a/src/bootstrap/src/core/build_steps/clean.rs b/src/bootstrap/src/core/build_steps/clean.rs
index 040690623a1..d857de96cce 100644
--- a/src/bootstrap/src/core/build_steps/clean.rs
+++ b/src/bootstrap/src/core/build_steps/clean.rs
@@ -226,6 +226,8 @@ where
         Err(ref e) if e.kind() == ErrorKind::PermissionDenied => {
             let m = t!(path.symlink_metadata());
             let mut p = m.permissions();
+            // this os not unix, so clippy gives FP
+            #[expect(clippy::permissions_set_readonly_false)]
             p.set_readonly(false);
             t!(fs::set_permissions(path, p));
             f(path).unwrap_or_else(|e| {
diff --git a/src/bootstrap/src/core/build_steps/clippy.rs b/src/bootstrap/src/core/build_steps/clippy.rs
index 6fb37e8cfc4..0884d86cc6d 100644
--- a/src/bootstrap/src/core/build_steps/clippy.rs
+++ b/src/bootstrap/src/core/build_steps/clippy.rs
@@ -20,14 +20,12 @@ const IGNORED_RULES_FOR_STD_AND_RUSTC: &[&str] = &[
     "wrong_self_convention",
 ];
 
-fn lint_args(builder: &Builder<'_>, ignored_rules: &[&str]) -> Vec<String> {
+fn lint_args(builder: &Builder<'_>, config: &LintConfig, ignored_rules: &[&str]) -> Vec<String> {
     fn strings<'a>(arr: &'a [&str]) -> impl Iterator<Item = String> + 'a {
         arr.iter().copied().map(String::from)
     }
 
-    let Subcommand::Clippy { fix, allow_dirty, allow_staged, allow, deny, warn, forbid } =
-        &builder.config.cmd
-    else {
+    let Subcommand::Clippy { fix, allow_dirty, allow_staged, .. } = &builder.config.cmd else {
         unreachable!("clippy::lint_args can only be called from `clippy` subcommands.");
     };
 
@@ -53,12 +51,12 @@ fn lint_args(builder: &Builder<'_>, ignored_rules: &[&str]) -> Vec<String> {
 
     args.extend(strings(&["--"]));
 
-    if deny.is_empty() && forbid.is_empty() {
+    if config.deny.is_empty() && config.forbid.is_empty() {
         args.extend(strings(&["--cap-lints", "warn"]));
     }
 
     let all_args = std::env::args().collect::<Vec<_>>();
-    args.extend(get_clippy_rules_in_order(&all_args, allow, deny, warn, forbid));
+    args.extend(get_clippy_rules_in_order(&all_args, config));
 
     args.extend(ignored_rules.iter().map(|lint| format!("-Aclippy::{}", lint)));
     args.extend(builder.config.free_args.clone());
@@ -68,21 +66,17 @@ fn lint_args(builder: &Builder<'_>, ignored_rules: &[&str]) -> Vec<String> {
 /// We need to keep the order of the given clippy lint rules before passing them.
 /// Since clap doesn't offer any useful interface for this purpose out of the box,
 /// we have to handle it manually.
-pub(crate) fn get_clippy_rules_in_order(
-    all_args: &[String],
-    allow_rules: &[String],
-    deny_rules: &[String],
-    warn_rules: &[String],
-    forbid_rules: &[String],
-) -> Vec<String> {
+pub fn get_clippy_rules_in_order(all_args: &[String], config: &LintConfig) -> Vec<String> {
     let mut result = vec![];
 
     for (prefix, item) in
-        [("-A", allow_rules), ("-D", deny_rules), ("-W", warn_rules), ("-F", forbid_rules)]
+        [("-A", &config.allow), ("-D", &config.deny), ("-W", &config.warn), ("-F", &config.forbid)]
     {
         item.iter().for_each(|v| {
             let rule = format!("{prefix}{v}");
-            let position = all_args.iter().position(|t| t == &rule || t == v).unwrap();
+            // Arguments added by bootstrap in LintConfig won't show up in the all_args list, so
+            // put them at the end of the command line.
+            let position = all_args.iter().position(|t| t == &rule || t == v).unwrap_or(usize::MAX);
             result.push((position, rule));
         });
     }
@@ -92,8 +86,41 @@ pub(crate) fn get_clippy_rules_in_order(
 }
 
 #[derive(Debug, Clone, PartialEq, Eq, Hash)]
+pub struct LintConfig {
+    pub allow: Vec<String>,
+    pub warn: Vec<String>,
+    pub deny: Vec<String>,
+    pub forbid: Vec<String>,
+}
+
+impl LintConfig {
+    fn new(builder: &Builder<'_>) -> Self {
+        match builder.config.cmd.clone() {
+            Subcommand::Clippy { allow, deny, warn, forbid, .. } => {
+                Self { allow, warn, deny, forbid }
+            }
+            _ => unreachable!("LintConfig can only be called from `clippy` subcommands."),
+        }
+    }
+
+    fn merge(&self, other: &Self) -> Self {
+        let merged = |self_attr: &[String], other_attr: &[String]| -> Vec<String> {
+            self_attr.iter().cloned().chain(other_attr.iter().cloned()).collect()
+        };
+        // This is written this way to ensure we get a compiler error if we add a new field.
+        Self {
+            allow: merged(&self.allow, &other.allow),
+            warn: merged(&self.warn, &other.warn),
+            deny: merged(&self.deny, &other.deny),
+            forbid: merged(&self.forbid, &other.forbid),
+        }
+    }
+}
+
+#[derive(Debug, Clone, PartialEq, Eq, Hash)]
 pub struct Std {
     pub target: TargetSelection,
+    config: LintConfig,
     /// Whether to lint only a subset of crates.
     crates: Vec<String>,
 }
@@ -108,7 +135,8 @@ impl Step for Std {
 
     fn make_run(run: RunConfig<'_>) {
         let crates = std_crates_for_run_make(&run);
-        run.builder.ensure(Std { target: run.target, crates });
+        let config = LintConfig::new(run.builder);
+        run.builder.ensure(Std { target: run.target, config, crates });
     }
 
     fn run(self, builder: &Builder<'_>) {
@@ -138,7 +166,7 @@ impl Step for Std {
         run_cargo(
             builder,
             cargo,
-            lint_args(builder, IGNORED_RULES_FOR_STD_AND_RUSTC),
+            lint_args(builder, &self.config, IGNORED_RULES_FOR_STD_AND_RUSTC),
             &libstd_stamp(builder, compiler, target),
             vec![],
             true,
@@ -150,6 +178,7 @@ impl Step for Std {
 #[derive(Debug, Clone, PartialEq, Eq, Hash)]
 pub struct Rustc {
     pub target: TargetSelection,
+    config: LintConfig,
     /// Whether to lint only a subset of crates.
     crates: Vec<String>,
 }
@@ -165,7 +194,8 @@ impl Step for Rustc {
 
     fn make_run(run: RunConfig<'_>) {
         let crates = run.make_run_crates(Alias::Compiler);
-        run.builder.ensure(Rustc { target: run.target, crates });
+        let config = LintConfig::new(run.builder);
+        run.builder.ensure(Rustc { target: run.target, config, crates });
     }
 
     /// Lints the compiler.
@@ -212,7 +242,7 @@ impl Step for Rustc {
         run_cargo(
             builder,
             cargo,
-            lint_args(builder, IGNORED_RULES_FOR_STD_AND_RUSTC),
+            lint_args(builder, &self.config, IGNORED_RULES_FOR_STD_AND_RUSTC),
             &librustc_stamp(builder, compiler, target),
             vec![],
             true,
@@ -232,6 +262,7 @@ macro_rules! lint_any {
         #[derive(Debug, Clone, Hash, PartialEq, Eq)]
         pub struct $name {
             pub target: TargetSelection,
+            config: LintConfig,
         }
 
         impl Step for $name {
@@ -243,8 +274,10 @@ macro_rules! lint_any {
             }
 
             fn make_run(run: RunConfig<'_>) {
+                let config = LintConfig::new(run.builder);
                 run.builder.ensure($name {
                     target: run.target,
+                    config,
                 });
             }
 
@@ -281,7 +314,7 @@ macro_rules! lint_any {
                 run_cargo(
                     builder,
                     cargo,
-                    lint_args(builder, &[]),
+                    lint_args(builder, &self.config, &[]),
                     &stamp,
                     vec![],
                     true,
@@ -319,3 +352,58 @@ lint_any!(
     Tidy, "src/tools/tidy", "tidy";
     TestFloatParse, "src/etc/test-float-parse", "test-float-parse";
 );
+
+#[derive(Debug, Clone, PartialEq, Eq, Hash)]
+pub struct CI {
+    target: TargetSelection,
+    config: LintConfig,
+}
+
+impl Step for CI {
+    type Output = ();
+    const DEFAULT: bool = false;
+
+    fn should_run(run: ShouldRun<'_>) -> ShouldRun<'_> {
+        run.alias("ci")
+    }
+
+    fn make_run(run: RunConfig<'_>) {
+        let config = LintConfig::new(run.builder);
+        run.builder.ensure(CI { target: run.target, config });
+    }
+
+    fn run(self, builder: &Builder<'_>) -> Self::Output {
+        builder.ensure(Bootstrap {
+            target: self.target,
+            config: self.config.merge(&LintConfig {
+                allow: vec![],
+                warn: vec![],
+                deny: vec!["warnings".into()],
+                forbid: vec![],
+            }),
+        });
+        let library_clippy_cfg = LintConfig {
+            allow: vec!["clippy::all".into()],
+            warn: vec![],
+            deny: vec!["clippy::correctness".into()],
+            forbid: vec![],
+        };
+        let compiler_clippy_cfg = LintConfig {
+            allow: vec!["clippy::all".into()],
+            warn: vec![],
+            deny: vec!["clippy::correctness".into(), "clippy::clone_on_ref_ptr".into()],
+            forbid: vec![],
+        };
+
+        builder.ensure(Std {
+            target: self.target,
+            config: self.config.merge(&library_clippy_cfg),
+            crates: vec![],
+        });
+        builder.ensure(Rustc {
+            target: self.target,
+            config: self.config.merge(&compiler_clippy_cfg),
+            crates: vec![],
+        });
+    }
+}
diff --git a/src/bootstrap/src/core/builder/mod.rs b/src/bootstrap/src/core/builder/mod.rs
index f1b3cf6da13..d59e0fa7288 100644
--- a/src/bootstrap/src/core/builder/mod.rs
+++ b/src/bootstrap/src/core/builder/mod.rs
@@ -839,6 +839,7 @@ impl<'a> Builder<'a> {
                 clippy::RustInstaller,
                 clippy::TestFloatParse,
                 clippy::Tidy,
+                clippy::CI,
             ),
             Kind::Check | Kind::Fix => describe!(
                 check::Std,
diff --git a/src/bootstrap/src/core/config/tests.rs b/src/bootstrap/src/core/config/tests.rs
index f911581b7ee..24f932a1724 100644
--- a/src/bootstrap/src/core/config/tests.rs
+++ b/src/bootstrap/src/core/config/tests.rs
@@ -9,7 +9,7 @@ use serde::Deserialize;
 
 use super::flags::Flags;
 use super::{ChangeIdWrapper, Config, RUSTC_IF_UNCHANGED_ALLOWED_PATHS};
-use crate::core::build_steps::clippy::get_clippy_rules_in_order;
+use crate::core::build_steps::clippy::{LintConfig, get_clippy_rules_in_order};
 use crate::core::build_steps::llvm;
 use crate::core::config::{LldMode, Target, TargetSelection, TomlConfig};
 
@@ -309,9 +309,10 @@ fn order_of_clippy_rules() {
     ];
     let config = Config::parse(Flags::parse(&args));
 
-    let actual = match &config.cmd {
+    let actual = match config.cmd.clone() {
         crate::Subcommand::Clippy { allow, deny, warn, forbid, .. } => {
-            get_clippy_rules_in_order(&args, &allow, &deny, &warn, &forbid)
+            let cfg = LintConfig { allow, deny, warn, forbid };
+            get_clippy_rules_in_order(&args, &cfg)
         }
         _ => panic!("invalid subcommand"),
     };
@@ -332,9 +333,10 @@ fn clippy_rule_separate_prefix() {
         vec!["clippy".to_string(), "-A clippy:all".to_string(), "-W clippy::style".to_string()];
     let config = Config::parse(Flags::parse(&args));
 
-    let actual = match &config.cmd {
+    let actual = match config.cmd.clone() {
         crate::Subcommand::Clippy { allow, deny, warn, forbid, .. } => {
-            get_clippy_rules_in_order(&args, &allow, &deny, &warn, &forbid)
+            let cfg = LintConfig { allow, deny, warn, forbid };
+            get_clippy_rules_in_order(&args, &cfg)
         }
         _ => panic!("invalid subcommand"),
     };
diff --git a/src/bootstrap/src/utils/helpers.rs b/src/bootstrap/src/utils/helpers.rs
index 7162007e9f0..3fb2330469a 100644
--- a/src/bootstrap/src/utils/helpers.rs
+++ b/src/bootstrap/src/utils/helpers.rs
@@ -145,7 +145,7 @@ pub fn symlink_dir(config: &Config, original: &Path, link: &Path) -> io::Result<
 
     #[cfg(windows)]
     fn symlink_dir_inner(target: &Path, junction: &Path) -> io::Result<()> {
-        junction::create(&target, &junction)
+        junction::create(target, junction)
     }
 }
 
diff --git a/src/ci/docker/host-x86_64/mingw-check/Dockerfile b/src/ci/docker/host-x86_64/mingw-check/Dockerfile
index fdc8a7310c8..f0afb570cc4 100644
--- a/src/ci/docker/host-x86_64/mingw-check/Dockerfile
+++ b/src/ci/docker/host-x86_64/mingw-check/Dockerfile
@@ -50,9 +50,7 @@ ENV SCRIPT \
            python3 ../x.py check --stage 0 --set build.optimized-compiler-builtins=false core alloc std --target=aarch64-unknown-linux-gnu,i686-pc-windows-msvc,i686-unknown-linux-gnu,x86_64-apple-darwin,x86_64-pc-windows-gnu,x86_64-pc-windows-msvc && \
            /scripts/check-default-config-profiles.sh && \
            python3 ../x.py check --target=i686-pc-windows-gnu --host=i686-pc-windows-gnu && \
-           python3 ../x.py clippy bootstrap -Dwarnings && \
-           python3 ../x.py clippy library -Aclippy::all -Dclippy::correctness && \
-           python3 ../x.py clippy compiler -Aclippy::all -Dclippy::correctness -Dclippy::clone_on_ref_ptr && \
+           python3 ../x.py clippy ci && \
            python3 ../x.py build --stage 0 src/tools/build-manifest && \
            python3 ../x.py test --stage 0 src/tools/compiletest && \
            python3 ../x.py test --stage 0 core alloc std test proc_macro && \
diff --git a/src/librustdoc/html/static/js/search.js b/src/librustdoc/html/static/js/search.js
index 4e1bbbbf59d..6c2b264063d 100644
--- a/src/librustdoc/html/static/js/search.js
+++ b/src/librustdoc/html/static/js/search.js
@@ -1106,6 +1106,153 @@ class RoaringBitmapBits {
     }
 }
 
+/**
+ * A prefix tree, used for name-based search.
+ *
+ * This data structure is used to drive prefix matches,
+ * such as matching the query "link" to `LinkedList`,
+ * and Lev-distance matches, such as matching the
+ * query "hahsmap" to `HashMap`. Substring matches,
+ * such as "list" to `LinkedList`, are done with a
+ * tailTable that deep-links into this trie.
+ *
+ * children
+ * : A [sparse array] of subtrees. The array index
+ *   is a charCode.
+ *
+ *   [sparse array]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Guide/
+ *     Indexed_collections#sparse_arrays
+ *
+ * matches
+ * : A list of search index IDs for this node.
+ *
+ * @typedef {{
+ *     children: [NameTrie],
+ *     matches: [number],
+ * }}
+ */
+class NameTrie {
+    constructor() {
+        this.children = [];
+        this.matches = [];
+    }
+    insert(name, id, tailTable) {
+        this.insertSubstring(name, 0, id, tailTable);
+    }
+    insertSubstring(name, substart, id, tailTable) {
+        const l = name.length;
+        if (substart === l) {
+            this.matches.push(id);
+        } else {
+            const sb = name.charCodeAt(substart);
+            let child;
+            if (this.children[sb] !== undefined) {
+                child = this.children[sb];
+            } else {
+                child = new NameTrie();
+                this.children[sb] = child;
+                let sste;
+                if (substart >= 2) {
+                    const tail = name.substring(substart - 2, substart + 1);
+                    if (tailTable.has(tail)) {
+                        sste = tailTable.get(tail);
+                    } else {
+                        sste = [];
+                        tailTable.set(tail, sste);
+                    }
+                    sste.push(child);
+                }
+            }
+            child.insertSubstring(name, substart + 1, id, tailTable);
+        }
+    }
+    search(name, tailTable) {
+        const results = new Set();
+        this.searchSubstringPrefix(name, 0, results);
+        if (results.size < MAX_RESULTS && name.length >= 3) {
+            const levParams = name.length >= 6 ?
+                new Lev2TParametricDescription(name.length) :
+                new Lev1TParametricDescription(name.length);
+            this.searchLev(name, 0, levParams, results);
+            const tail = name.substring(0, 3);
+            if (tailTable.has(tail)) {
+                for (const entry of tailTable.get(tail)) {
+                    entry.searchSubstringPrefix(name, 3, results);
+                }
+            }
+        }
+        return [...results];
+    }
+    searchSubstringPrefix(name, substart, results) {
+        const l = name.length;
+        if (substart === l) {
+            for (const match of this.matches) {
+                results.add(match);
+            }
+            // breadth-first traversal orders prefix matches by length
+            let unprocessedChildren = [];
+            for (const child of this.children) {
+                if (child) {
+                    unprocessedChildren.push(child);
+                }
+            }
+            let nextSet = [];
+            while (unprocessedChildren.length !== 0) {
+                const next = unprocessedChildren.pop();
+                for (const child of next.children) {
+                    if (child) {
+                        nextSet.push(child);
+                    }
+                }
+                for (const match of next.matches) {
+                    results.add(match);
+                }
+                if (unprocessedChildren.length === 0) {
+                    const tmp = unprocessedChildren;
+                    unprocessedChildren = nextSet;
+                    nextSet = tmp;
+                }
+            }
+        } else {
+            const sb = name.charCodeAt(substart);
+            if (this.children[sb] !== undefined) {
+                this.children[sb].searchSubstringPrefix(name, substart + 1, results);
+            }
+        }
+    }
+    searchLev(name, substart, levParams, results) {
+        const stack = [[this, 0]];
+        const n = levParams.n;
+        while (stack.length !== 0) {
+            const [trie, levState] = stack.pop();
+            for (const [charCode, child] of trie.children.entries()) {
+                if (!child) {
+                    continue;
+                }
+                const levPos = levParams.getPosition(levState);
+                const vector = levParams.getVector(
+                    name,
+                    charCode,
+                    levPos,
+                    Math.min(name.length, levPos + (2 * n) + 1),
+                );
+                const newLevState = levParams.transition(
+                    levState,
+                    levPos,
+                    vector,
+                );
+                if (newLevState >= 0) {
+                    stack.push([child, newLevState]);
+                    if (levParams.isAccept(newLevState)) {
+                        for (const match of child.matches) {
+                            results.add(match);
+                        }
+                    }
+                }
+            }
+        }
+    }
+}
 
 class DocSearch {
     constructor(rawSearchIndex, rootPath, searchState) {
@@ -1211,6 +1358,20 @@ class DocSearch {
         this.TYPES_POOL = new Map();
 
         /**
+         * A trie for finding items by name.
+         * This is used for edit distance and prefix finding.
+         *
+         * @type {NameTrie}
+         */
+        this.nameTrie = new NameTrie();
+
+        /**
+         * Find items by 3-substring. This is a map from three-char
+         * prefixes into lists of subtries.
+         */
+        this.tailTable = new Map();
+
+        /**
          *  @type {Array<Row>}
          */
         this.searchIndex = this.buildIndex(rawSearchIndex);
@@ -1613,6 +1774,7 @@ class DocSearch {
             // This object should have exactly the same set of fields as the "row"
             // object defined below. Your JavaScript runtime will thank you.
             // https://mathiasbynens.be/notes/shapes-ics
+            let normalizedName = crate.indexOf("_") === -1 ? crate : crate.replace(/_/g, "");
             const crateRow = {
                 crate,
                 ty: 3, // == ExternCrate
@@ -1627,10 +1789,11 @@ class DocSearch {
                 paramNames: lastParamNames,
                 id,
                 word: crate,
-                normalizedName: crate.indexOf("_") === -1 ? crate : crate.replace(/_/g, ""),
+                normalizedName,
                 bitIndex: 0,
                 implDisambiguator: null,
             };
+            this.nameTrie.insert(normalizedName, id, this.tailTable);
             id += 1;
             searchIndex.push(crateRow);
             currentIndex += 1;
@@ -1749,6 +1912,7 @@ class DocSearch {
                 // This object should have exactly the same set of fields as the "crateRow"
                 // object defined above.
                 const itemParentIdx = itemParentIdxDecoder.next();
+                normalizedName = word.indexOf("_") === -1 ? word : word.replace(/_/g, "");
                 const row = {
                     crate,
                     ty: itemTypes.charCodeAt(i) - 65, // 65 = "A"
@@ -1763,11 +1927,12 @@ class DocSearch {
                     paramNames,
                     id,
                     word,
-                    normalizedName: word.indexOf("_") === -1 ? word : word.replace(/_/g, ""),
+                    normalizedName,
                     bitIndex,
                     implDisambiguator: implDisambiguator.has(i) ?
                         implDisambiguator.get(i) : null,
                 };
+                this.nameTrie.insert(normalizedName, id, this.tailTable);
                 id += 1;
                 searchIndex.push(row);
                 lastPath = row.path;
@@ -2060,6 +2225,7 @@ class DocSearch {
                     "/index.html#reexport." + name;
             } else if (type === "primitive" || type === "keyword") {
                 displayPath = "";
+                exactPath = "";
                 href = this.rootPath + path.replace(/::/g, "/") +
                     "/" + type + "." + name + ".html";
             } else if (type === "externcrate") {
@@ -2075,6 +2241,7 @@ class DocSearch {
 
                 if (parentType === "primitive") {
                     displayPath = myparent.name + "::";
+                    exactPath = myparent.name;
                 } else if (type === "structfield" && parentType === "variant") {
                     // Structfields belonging to variants are special: the
                     // final path element is the enum name.
@@ -3589,96 +3756,6 @@ class DocSearch {
         }
 
         /**
-         * This function is called in case the query is only one element (with or without generics).
-         * This element will be compared to arguments' and returned values' items and also to items.
-         *
-         * Other important thing to note: since there is only one element, we use edit
-         * distance for name comparisons.
-         *
-         * @param {Row} row
-         * @param {integer} pos              - Position in the `searchIndex`.
-         * @param {QueryElement} elem        - The element from the parsed query.
-         * @param {Results} results_others   - Unqualified results (not in arguments nor in
-         *                                     returned values).
-         * @param {Results} results_in_args  - Matching arguments results.
-         * @param {Results} results_returned - Matching returned arguments results.
-         */
-        function handleSingleArg(
-            row,
-            pos,
-            elem,
-            results_others,
-            results_in_args,
-            results_returned,
-            maxEditDistance,
-        ) {
-            if (!row || (filterCrates !== null && row.crate !== filterCrates)) {
-                return;
-            }
-            let path_dist = 0;
-            const fullId = row.id;
-
-            // fpDist is a minimum possible type distance, where "type distance" is the number of
-            // atoms in the function not present in the query
-            const tfpDist = compareTypeFingerprints(
-                fullId,
-                parsedQuery.typeFingerprint,
-            );
-            if (tfpDist !== null) {
-                const in_args = row.type && row.type.inputs
-                    && checkIfInList(row.type.inputs, elem, row.type.where_clause, null, 0);
-                const returned = row.type && row.type.output
-                    && checkIfInList(row.type.output, elem, row.type.where_clause, null, 0);
-                if (in_args) {
-                    results_in_args.max_dist = Math.max(results_in_args.max_dist || 0, tfpDist);
-                    const maxDist = results_in_args.size < MAX_RESULTS ?
-                        (tfpDist + 1) :
-                        results_in_args.max_dist;
-                    addIntoResults(results_in_args, fullId, pos, -1, tfpDist, 0, maxDist);
-                }
-                if (returned) {
-                    results_returned.max_dist = Math.max(results_returned.max_dist || 0, tfpDist);
-                    const maxDist = results_returned.size < MAX_RESULTS ?
-                        (tfpDist + 1) :
-                        results_returned.max_dist;
-                    addIntoResults(results_returned, fullId, pos, -1, tfpDist, 0, maxDist);
-                }
-            }
-
-            if (!typePassesFilter(elem.typeFilter, row.ty)) {
-                return;
-            }
-
-            let index = row.word.indexOf(elem.pathLast);
-            const normalizedIndex = row.normalizedName.indexOf(elem.pathLast);
-            if (index === -1 || (index > normalizedIndex && normalizedIndex !== -1)) {
-                index = normalizedIndex;
-            }
-
-            if (elem.fullPath.length > 1) {
-                path_dist = checkPath(elem.pathWithoutLast, row);
-                if (path_dist === null) {
-                    return;
-                }
-            }
-
-            if (parsedQuery.literalSearch) {
-                if (row.word === elem.pathLast) {
-                    addIntoResults(results_others, fullId, pos, index, 0, path_dist);
-                }
-                return;
-            }
-
-            const dist = editDistance(row.normalizedName, elem.normalizedPathLast, maxEditDistance);
-
-            if (index === -1 && dist > maxEditDistance) {
-                return;
-            }
-
-            addIntoResults(results_others, fullId, pos, index, dist, path_dist, maxEditDistance);
-        }
-
-        /**
          * This function is called in case the query has more than one element. In this case, it'll
          * try to match the items which validates all the elements. For `aa -> bb` will look for
          * functions which have a parameter `aa` and has `bb` in its returned values.
@@ -3893,23 +3970,79 @@ class DocSearch {
             }
 
             if (parsedQuery.foundElems === 1 && !parsedQuery.hasReturnArrow) {
-                if (parsedQuery.elems.length === 1) {
-                    const elem = parsedQuery.elems[0];
-                    const length = this.searchIndex.length;
-                    for (let i = 0, nSearchIndex = length; i < nSearchIndex; ++i) {
-                        // It means we want to check for this element everywhere (in names, args and
-                        // returned).
-                        handleSingleArg(
-                            this.searchIndex[i],
-                            i,
-                            elem,
+                const elem = parsedQuery.elems[0];
+                for (const id of this.nameTrie.search(elem.normalizedPathLast, this.tailTable)) {
+                    const row = this.searchIndex[id];
+                    if (!typePassesFilter(elem.typeFilter, row.ty) ||
+                        (filterCrates !== null && row.crate !== filterCrates)) {
+                        continue;
+                    }
+
+                    let pathDist = 0;
+                    if (elem.fullPath.length > 1) {
+                        pathDist = checkPath(elem.pathWithoutLast, row);
+                        if (pathDist === null) {
+                            continue;
+                        }
+                    }
+
+                    if (parsedQuery.literalSearch) {
+                        if (row.word === elem.pathLast) {
+                            addIntoResults(results_others, row.id, id, 0, 0, pathDist);
+                        }
+                    } else {
+                        addIntoResults(
                             results_others,
-                            results_in_args,
-                            results_returned,
+                            row.id,
+                            id,
+                            row.normalizedName.indexOf(elem.normalizedPathLast),
+                            editDistance(
+                                row.normalizedName,
+                                elem.normalizedPathLast,
+                                maxEditDistance,
+                            ),
+                            pathDist,
                             maxEditDistance,
                         );
                     }
                 }
+                const length = this.searchIndex.length;
+                for (let i = 0, nSearchIndex = length; i < nSearchIndex; ++i) {
+                    const row = this.searchIndex[i];
+                    if (filterCrates !== null && row.crate !== filterCrates) {
+                        continue;
+                    }
+                    const tfpDist = compareTypeFingerprints(
+                        row.id,
+                        parsedQuery.typeFingerprint,
+                    );
+                    if (tfpDist !== null) {
+                        const in_args = row.type && row.type.inputs
+                            && checkIfInList(row.type.inputs, elem, row.type.where_clause, null, 0);
+                        const returned = row.type && row.type.output
+                            && checkIfInList(row.type.output, elem, row.type.where_clause, null, 0);
+                        if (in_args) {
+                            results_in_args.max_dist = Math.max(
+                                results_in_args.max_dist || 0,
+                                tfpDist,
+                            );
+                            const maxDist = results_in_args.size < MAX_RESULTS ?
+                                (tfpDist + 1) :
+                                results_in_args.max_dist;
+                            addIntoResults(results_in_args, row.id, i, -1, tfpDist, 0, maxDist);
+                        }
+                        if (returned) {
+                            results_returned.max_dist = Math.max(
+                                results_returned.max_dist || 0,
+                                tfpDist,
+                            );
+                            const maxDist = results_returned.size < MAX_RESULTS ?
+                                (tfpDist + 1) :
+                                results_returned.max_dist;
+                            addIntoResults(results_returned, row.id, i, -1, tfpDist, 0, maxDist);
+                        }
+                    }
+                }
             } else if (parsedQuery.foundElems > 0) {
                 // Sort input and output so that generic type variables go first and
                 // types with generic parameters go last.
@@ -4653,3 +4786,477 @@ if (typeof window !== "undefined") {
     // exports.
     initSearch(new Map());
 }
+
+// Parts of this code are based on Lucene, which is licensed under the
+// Apache/2.0 license.
+// More information found here:
+// https://fossies.org/linux/lucene/lucene/core/src/java/org/apache/lucene/util/automaton/
+//   LevenshteinAutomata.java
+class ParametricDescription {
+    constructor(w, n, minErrors) {
+        this.w = w;
+        this.n = n;
+        this.minErrors = minErrors;
+    }
+    isAccept(absState) {
+        const state = Math.floor(absState / (this.w + 1));
+        const offset = absState % (this.w + 1);
+        return this.w - offset + this.minErrors[state] <= this.n;
+    }
+    getPosition(absState) {
+        return absState % (this.w + 1);
+    }
+    getVector(name, charCode, pos, end) {
+        let vector = 0;
+        for (let i = pos; i < end; i += 1) {
+            vector = vector << 1;
+            if (name.charCodeAt(i) === charCode) {
+                vector |= 1;
+            }
+        }
+        return vector;
+    }
+    unpack(data, index, bitsPerValue) {
+        const bitLoc = (bitsPerValue * index);
+        const dataLoc = bitLoc >> 5;
+        const bitStart = bitLoc & 31;
+        if (bitStart + bitsPerValue <= 32) {
+            // not split
+            return ((data[dataLoc] >> bitStart) & this.MASKS[bitsPerValue - 1]);
+        } else {
+            // split
+            const part = 32 - bitStart;
+            return ~~(((data[dataLoc] >> bitStart) & this.MASKS[part - 1]) +
+                ((data[1 + dataLoc] & this.MASKS[bitsPerValue - part - 1]) << part));
+        }
+    }
+}
+ParametricDescription.prototype.MASKS = new Int32Array([
+    0x1, 0x3, 0x7, 0xF,
+    0x1F, 0x3F, 0x7F, 0xFF,
+    0x1FF, 0x3F, 0x7FF, 0xFFF,
+    0x1FFF, 0x3FFF, 0x7FFF, 0xFFFF,
+    0x1FFFF, 0x3FFFF, 0x7FFFF, 0xFFFFF,
+    0x1FFFFF, 0x3FFFFF, 0x7FFFFF, 0xFFFFFF,
+    0x1FFFFFF, 0x3FFFFFF, 0x7FFFFFF, 0xFFFFFFF,
+    0x1FFFFFFF, 0x3FFFFFFF, 0x7FFFFFFF, 0xFFFFFFFF,
+]);
+
+// The following code was generated with the moman/finenight pkg
+// This package is available under the MIT License, see NOTICE.txt
+// for more details.
+// This class is auto-generated, Please do not modify it directly.
+// You should modify the https://gitlab.com/notriddle/createAutomata.py instead.
+// The following code was generated with the moman/finenight pkg
+// This package is available under the MIT License, see NOTICE.txt
+// for more details.
+// This class is auto-generated, Please do not modify it directly.
+// You should modify https://gitlab.com/notriddle/moman-rustdoc instead.
+
+class Lev2TParametricDescription extends ParametricDescription {
+    /**
+     * @param {number} absState
+     * @param {number} position
+     * @param {number} vector
+     * @returns {number}
+    */
+    transition(absState, position, vector) {
+        let state = Math.floor(absState / (this.w + 1));
+        let offset = absState % (this.w + 1);
+
+        if (position === this.w) {
+            if (state < 3) { // eslint-disable-line no-lonely-if
+                const loc = Math.imul(vector, 3) + state;
+                offset += this.unpack(this.offsetIncrs0, loc, 1);
+                state = this.unpack(this.toStates0, loc, 2) - 1;
+            }
+        } else if (position === this.w - 1) {
+            if (state < 5) { // eslint-disable-line no-lonely-if
+                const loc = Math.imul(vector, 5) + state;
+                offset += this.unpack(this.offsetIncrs1, loc, 1);
+                state = this.unpack(this.toStates1, loc, 3) - 1;
+            }
+        } else if (position === this.w - 2) {
+            if (state < 13) { // eslint-disable-line no-lonely-if
+                const loc = Math.imul(vector, 13) + state;
+                offset += this.unpack(this.offsetIncrs2, loc, 2);
+                state = this.unpack(this.toStates2, loc, 4) - 1;
+            }
+        } else if (position === this.w - 3) {
+            if (state < 28) { // eslint-disable-line no-lonely-if
+                const loc = Math.imul(vector, 28) + state;
+                offset += this.unpack(this.offsetIncrs3, loc, 2);
+                state = this.unpack(this.toStates3, loc, 5) - 1;
+            }
+        } else if (position === this.w - 4) {
+            if (state < 45) { // eslint-disable-line no-lonely-if
+                const loc = Math.imul(vector, 45) + state;
+                offset += this.unpack(this.offsetIncrs4, loc, 3);
+                state = this.unpack(this.toStates4, loc, 6) - 1;
+            }
+        } else {
+            if (state < 45) { // eslint-disable-line no-lonely-if
+                const loc = Math.imul(vector, 45) + state;
+                offset += this.unpack(this.offsetIncrs5, loc, 3);
+                state = this.unpack(this.toStates5, loc, 6) - 1;
+            }
+        }
+
+        if (state === -1) {
+            // null state
+            return -1;
+        } else {
+            // translate back to abs
+            return Math.imul(state, this.w + 1) + offset;
+        }
+    }
+
+    // state map
+    //   0 -> [(0, 0)]
+    //   1 -> [(0, 1)]
+    //   2 -> [(0, 2)]
+    //   3 -> [(0, 1), (1, 1)]
+    //   4 -> [(0, 2), (1, 2)]
+    //   5 -> [(0, 1), (1, 1), (2, 1)]
+    //   6 -> [(0, 2), (1, 2), (2, 2)]
+    //   7 -> [(0, 1), (2, 1)]
+    //   8 -> [(0, 1), (2, 2)]
+    //   9 -> [(0, 2), (2, 1)]
+    //   10 -> [(0, 2), (2, 2)]
+    //   11 -> [t(0, 1), (0, 1), (1, 1), (2, 1)]
+    //   12 -> [t(0, 2), (0, 2), (1, 2), (2, 2)]
+    //   13 -> [(0, 2), (1, 2), (2, 2), (3, 2)]
+    //   14 -> [(0, 1), (1, 1), (3, 2)]
+    //   15 -> [(0, 1), (2, 2), (3, 2)]
+    //   16 -> [(0, 1), (3, 2)]
+    //   17 -> [(0, 1), t(1, 2), (2, 2), (3, 2)]
+    //   18 -> [(0, 2), (1, 2), (3, 1)]
+    //   19 -> [(0, 2), (1, 2), (3, 2)]
+    //   20 -> [(0, 2), (1, 2), t(1, 2), (2, 2), (3, 2)]
+    //   21 -> [(0, 2), (2, 1), (3, 1)]
+    //   22 -> [(0, 2), (2, 2), (3, 2)]
+    //   23 -> [(0, 2), (3, 1)]
+    //   24 -> [(0, 2), (3, 2)]
+    //   25 -> [(0, 2), t(1, 2), (1, 2), (2, 2), (3, 2)]
+    //   26 -> [t(0, 2), (0, 2), (1, 2), (2, 2), (3, 2)]
+    //   27 -> [t(0, 2), (0, 2), (1, 2), (3, 1)]
+    //   28 -> [(0, 2), (1, 2), (2, 2), (3, 2), (4, 2)]
+    //   29 -> [(0, 2), (1, 2), (2, 2), (4, 2)]
+    //   30 -> [(0, 2), (1, 2), (2, 2), t(2, 2), (3, 2), (4, 2)]
+    //   31 -> [(0, 2), (1, 2), (3, 2), (4, 2)]
+    //   32 -> [(0, 2), (1, 2), (4, 2)]
+    //   33 -> [(0, 2), (1, 2), t(1, 2), (2, 2), (3, 2), (4, 2)]
+    //   34 -> [(0, 2), (1, 2), t(2, 2), (2, 2), (3, 2), (4, 2)]
+    //   35 -> [(0, 2), (2, 1), (4, 2)]
+    //   36 -> [(0, 2), (2, 2), (3, 2), (4, 2)]
+    //   37 -> [(0, 2), (2, 2), (4, 2)]
+    //   38 -> [(0, 2), (3, 2), (4, 2)]
+    //   39 -> [(0, 2), (4, 2)]
+    //   40 -> [(0, 2), t(1, 2), (1, 2), (2, 2), (3, 2), (4, 2)]
+    //   41 -> [(0, 2), t(2, 2), (2, 2), (3, 2), (4, 2)]
+    //   42 -> [t(0, 2), (0, 2), (1, 2), (2, 2), (3, 2), (4, 2)]
+    //   43 -> [t(0, 2), (0, 2), (1, 2), (2, 2), (4, 2)]
+    //   44 -> [t(0, 2), (0, 2), (1, 2), (2, 2), t(2, 2), (3, 2), (4, 2)]
+
+
+    /** @param {number} w - length of word being checked */
+    constructor(w) {
+        super(w, 2, new Int32Array([
+            0,1,2,0,1,-1,0,-1,0,-1,0,-1,0,-1,-1,-1,-1,-1,-2,-1,-1,-2,-1,-2,
+            -1,-1,-1,-2,-2,-2,-2,-2,-2,-2,-2,-2,-2,-2,-2,-2,-2,-2,-2,-2,-2,
+        ]));
+    }
+}
+
+Lev2TParametricDescription.prototype.toStates0 = /*2 bits per value */ new Int32Array([
+    0xe,
+]);
+Lev2TParametricDescription.prototype.offsetIncrs0 = /*1 bits per value */ new Int32Array([
+    0x0,
+]);
+
+Lev2TParametricDescription.prototype.toStates1 = /*3 bits per value */ new Int32Array([
+    0x1a688a2c,
+]);
+Lev2TParametricDescription.prototype.offsetIncrs1 = /*1 bits per value */ new Int32Array([
+    0x3e0,
+]);
+
+Lev2TParametricDescription.prototype.toStates2 = /*4 bits per value */ new Int32Array([
+    0x70707054,0xdc07035,0x3dd3a3a,0x2323213a,
+    0x15435223,0x22545432,0x5435,
+]);
+Lev2TParametricDescription.prototype.offsetIncrs2 = /*2 bits per value */ new Int32Array([
+    0x80000,0x55582088,0x55555555,0x55,
+]);
+
+Lev2TParametricDescription.prototype.toStates3 = /*5 bits per value */ new Int32Array([
+    0x1c0380a4,0x700a570,0xca529c0,0x180a00,
+    0xa80af180,0xc5498e60,0x5a546398,0x8c4300e8,
+    0xac18c601,0xd8d43501,0x863500ad,0x51976d6a,
+    0x8ca0180a,0xc3501ac2,0xb0c5be16,0x76dda8a5,
+    0x18c4519,0xc41294a,0xe248d231,0x1086520c,
+    0xce31ac42,0x13946358,0x2d0348c4,0x6732d494,
+    0x1ad224a5,0xd635ad4b,0x520c4139,0xce24948,
+    0x22110a52,0x58ce729d,0xc41394e3,0x941cc520,
+    0x90e732d4,0x4729d224,0x39ce35ad,
+]);
+Lev2TParametricDescription.prototype.offsetIncrs3 = /*2 bits per value */ new Int32Array([
+    0x80000,0xc0c830,0x300f3c30,0x2200fcff,
+    0xcaa00a08,0x3c2200a8,0xa8fea00a,0x55555555,
+    0x55555555,0x55555555,0x55555555,0x55555555,
+    0x55555555,0x55555555,
+]);
+
+Lev2TParametricDescription.prototype.toStates4 = /*6 bits per value */ new Int32Array([
+    0x801c0144,0x1453803,0x14700038,0xc0005145,
+    0x1401,0x14,0x140000,0x0,
+    0x510000,0x6301f007,0x301f00d1,0xa186178,
+    0xc20ca0c3,0xc20c30,0xc30030c,0xc00c00cd,
+    0xf0c00c30,0x4c054014,0xc30944c3,0x55150c34,
+    0x8300550,0x430c0143,0x50c31,0xc30850c,
+    0xc3143000,0x50053c50,0x5130d301,0x850d30c2,
+    0x30a08608,0xc214414,0x43142145,0x21450031,
+    0x1400c314,0x4c143145,0x32832803,0x28014d6c,
+    0xcd34a0c3,0x1c50c76,0x1c314014,0x430c30c3,
+    0x1431,0xc300500,0xca00d303,0xd36d0e40,
+    0x90b0e400,0xcb2abb2c,0x70c20ca1,0x2c32ca2c,
+    0xcd2c70cb,0x31c00c00,0x34c2c32c,0x5583280,
+    0x558309b7,0x6cd6ca14,0x430850c7,0x51c51401,
+    0x1430c714,0xc3087,0x71451450,0xca00d30,
+    0xc26dc156,0xb9071560,0x1cb2abb2,0xc70c2144,
+    0xb1c51ca1,0x1421c70c,0xc51c00c3,0x30811c51,
+    0x24324308,0xc51031c2,0x70820820,0x5c33830d,
+    0xc33850c3,0x30c30c30,0xc30c31c,0x451450c3,
+    0x20c20c20,0xda0920d,0x5145914f,0x36596114,
+    0x51965865,0xd9643653,0x365a6590,0x51964364,
+    0x43081505,0x920b2032,0x2c718b28,0xd7242249,
+    0x35cb28b0,0x2cb3872c,0x972c30d7,0xb0c32cb2,
+    0x4e1c75c,0xc80c90c2,0x62ca2482,0x4504171c,
+    0xd65d9610,0x33976585,0xd95cb5d,0x4b5ca5d7,
+    0x73975c36,0x10308138,0xc2245105,0x41451031,
+    0x14e24208,0xc35c3387,0x51453851,0x1c51c514,
+    0xc70c30c3,0x20451450,0x14f1440c,0x4f0da092,
+    0x4513d41,0x6533944d,0x1350e658,0xe1545055,
+    0x64365a50,0x5519383,0x51030815,0x28920718,
+    0x441c718b,0x714e2422,0x1c35cb28,0x4e1c7387,
+    0xb28e1c51,0x5c70c32c,0xc204e1c7,0x81c61440,
+    0x1c62ca24,0xd04503ce,0x85d63944,0x39338e65,
+    0x8e154387,0x364b5ca3,0x38739738,
+]);
+Lev2TParametricDescription.prototype.offsetIncrs4 = /*3 bits per value */ new Int32Array([
+    0x10000000,0xc00000,0x60061,0x400,
+    0x0,0x80010008,0x249248a4,0x8229048,
+    0x2092,0x6c3603,0xb61b6c30,0x6db6036d,
+    0xdb6c0,0x361b0180,0x91b72000,0xdb11b71b,
+    0x6db6236,0x1008200,0x12480012,0x24924906,
+    0x48200049,0x80410002,0x24000900,0x4924a489,
+    0x10822492,0x20800125,0x48360,0x9241b692,
+    0x6da4924,0x40009268,0x241b010,0x291b4900,
+    0x6d249249,0x49493423,0x92492492,0x24924924,
+    0x49249249,0x92492492,0x24924924,0x49249249,
+    0x92492492,0x24924924,0x49249249,0x92492492,
+    0x24924924,0x49249249,0x92492492,0x24924924,
+    0x49249249,0x92492492,0x24924924,0x49249249,
+    0x92492492,0x24924924,0x49249249,0x92492492,
+    0x24924924,0x49249249,0x92492492,0x24924924,
+    0x49249249,0x92492492,0x24924924,0x49249249,
+    0x92492492,0x24924924,0x49249249,0x2492,
+]);
+
+Lev2TParametricDescription.prototype.toStates5 = /*6 bits per value */ new Int32Array([
+    0x801c0144,0x1453803,0x14700038,0xc0005145,
+    0x1401,0x14,0x140000,0x0,
+    0x510000,0x4e00e007,0xe0051,0x3451451c,
+    0xd015000,0x30cd0000,0xc30c30c,0xc30c30d4,
+    0x40c30c30,0x7c01c014,0xc03458c0,0x185e0c07,
+    0x2830c286,0x830c3083,0xc30030,0x33430c,
+    0x30c3003,0x70051030,0x16301f00,0x8301f00d,
+    0x30a18617,0xc20ca0c,0x431420c3,0xb1450c51,
+    0x14314315,0x4f143145,0x34c05401,0x4c30944c,
+    0x55150c3,0x30830055,0x1430c014,0xc00050c3,
+    0xc30850,0xc314300,0x150053c5,0x25130d30,
+    0x5430d30c,0xc0354154,0x300d0c90,0x1cb2cd0c,
+    0xc91cb0c3,0x72c30cb2,0x14f1cb2c,0xc34c0540,
+    0x34c30944,0x82182214,0x851050c2,0x50851430,
+    0x1400c50c,0x30c5085,0x50c51450,0x150053c,
+    0xc25130d3,0x8850d30,0x1430a086,0x450c2144,
+    0x51cb1c21,0x1c91c70c,0xc71c314b,0x34c1cb1,
+    0x6c328328,0xc328014d,0x76cd34a0,0x1401c50c,
+    0xc31c3140,0x31430c30,0x14,0x30c3005,
+    0xa0ca00d3,0x535b0c,0x4d2830ca,0x514369b3,
+    0xc500d01,0x5965965a,0x30d46546,0x6435030c,
+    0x8034c659,0xdb439032,0x2c390034,0xcaaecb24,
+    0x30832872,0xcb28b1c,0x4b1c32cb,0x70030033,
+    0x30b0cb0c,0xe40ca00d,0x400d36d0,0xb2c90b0e,
+    0xca1cb2ab,0xa2c70c20,0x6575d95c,0x4315b5ce,
+    0x95c53831,0x28034c5d,0x9b705583,0xa1455830,
+    0xc76cd6c,0x40143085,0x71451c51,0x871430c,
+    0x450000c3,0xd3071451,0x1560ca00,0x560c26dc,
+    0xb35b2851,0xc914369,0x1a14500d,0x46593945,
+    0xcb2c939,0x94507503,0x328034c3,0x9b70558,
+    0xe41c5583,0x72caaeca,0x1c308510,0xc7147287,
+    0x50871c32,0x1470030c,0xd307147,0xc1560ca0,
+    0x1560c26d,0xabb2b907,0x21441cb2,0x38a1c70c,
+    0x8e657394,0x314b1c93,0x39438738,0x43083081,
+    0x31c22432,0x820c510,0x830d7082,0x50c35c33,
+    0xc30c338,0xc31c30c3,0x50c30c30,0xc204514,
+    0x890c90c2,0x31440c70,0xa8208208,0xea0df0c3,
+    0x8a231430,0xa28a28a2,0x28a28a1e,0x1861868a,
+    0x48308308,0xc3682483,0x14516453,0x4d965845,
+    0xd4659619,0x36590d94,0xd969964,0x546590d9,
+    0x20c20541,0x920d20c,0x5914f0da,0x96114514,
+    0x65865365,0xe89d3519,0x99e7a279,0x9e89e89e,
+    0x81821827,0xb2032430,0x18b28920,0x422492c7,
+    0xb28b0d72,0x3872c35c,0xc30d72cb,0x32cb2972,
+    0x1c75cb0c,0xc90c204e,0xa2482c80,0x24b1c62c,
+    0xc3a89089,0xb0ea2e42,0x9669a31c,0xa4966a28,
+    0x59a8a269,0x8175e7a,0xb203243,0x718b2892,
+    0x4114105c,0x17597658,0x74ce5d96,0x5c36572d,
+    0xd92d7297,0xe1ce5d70,0xc90c204,0xca2482c8,
+    0x4171c62,0x5d961045,0x976585d6,0x79669533,
+    0x964965a2,0x659689e6,0x308175e7,0x24510510,
+    0x451031c2,0xe2420841,0x5c338714,0x453851c3,
+    0x51c51451,0xc30c31c,0x451450c7,0x41440c20,
+    0xc708914,0x82105144,0xf1c58c90,0x1470ea0d,
+    0x61861863,0x8a1e85e8,0x8687a8a2,0x3081861,
+    0x24853c51,0x5053c368,0x1341144f,0x96194ce5,
+    0x1544d439,0x94385514,0xe0d90d96,0x5415464,
+    0x4f1440c2,0xf0da0921,0x4513d414,0x533944d0,
+    0x350e6586,0x86082181,0xe89e981d,0x18277689,
+    0x10308182,0x89207185,0x41c718b2,0x14e24224,
+    0xc35cb287,0xe1c73871,0x28e1c514,0xc70c32cb,
+    0x204e1c75,0x1c61440c,0xc62ca248,0x90891071,
+    0x2e41c58c,0xa31c70ea,0xe86175e7,0xa269a475,
+    0x5e7a57a8,0x51030817,0x28920718,0xf38718b,
+    0xe5134114,0x39961758,0xe1ce4ce,0x728e3855,
+    0x5ce0d92d,0xc204e1ce,0x81c61440,0x1c62ca24,
+    0xd04503ce,0x85d63944,0x75338e65,0x5d86075e,
+    0x89e69647,0x75e76576,
+]);
+Lev2TParametricDescription.prototype.offsetIncrs5 = /*3 bits per value */ new Int32Array([
+    0x10000000,0xc00000,0x60061,0x400,
+    0x0,0x60000008,0x6b003080,0xdb6ab6db,
+    0x2db6,0x800400,0x49245240,0x11482412,
+    0x104904,0x40020000,0x92292000,0xa4b25924,
+    0x9649658,0xd80c000,0xdb0c001b,0x80db6d86,
+    0x6db01b6d,0xc0600003,0x86000d86,0x6db6c36d,
+    0xddadb6ed,0x300001b6,0x6c360,0xe37236e4,
+    0x46db6236,0xdb6c,0x361b018,0xb91b7200,
+    0x6dbb1b71,0x6db763,0x20100820,0x61248001,
+    0x92492490,0x24820004,0x8041000,0x92400090,
+    0x24924830,0x555b6a49,0x2080012,0x20004804,
+    0x49252449,0x84112492,0x4000928,0x240201,
+    0x92922490,0x58924924,0x49456,0x120d8082,
+    0x6da4800,0x69249249,0x249a01b,0x6c04100,
+    0x6d240009,0x92492483,0x24d5adb4,0x60208001,
+    0x92000483,0x24925236,0x6846da49,0x10400092,
+    0x241b0,0x49291b49,0x636d2492,0x92494935,
+    0x24924924,0x49249249,0x92492492,0x24924924,
+    0x49249249,0x92492492,0x24924924,0x49249249,
+    0x92492492,0x24924924,0x49249249,0x92492492,
+    0x24924924,0x49249249,0x92492492,0x24924924,
+    0x49249249,0x92492492,0x24924924,0x49249249,
+    0x92492492,0x24924924,0x49249249,0x92492492,
+    0x24924924,0x49249249,0x92492492,0x24924924,
+    0x49249249,0x92492492,0x24924924,0x49249249,
+    0x92492492,0x24924924,0x49249249,0x92492492,
+    0x24924924,0x49249249,0x92492492,0x24924924,
+    0x49249249,0x92492492,0x24924924,0x49249249,
+    0x92492492,0x24924924,0x49249249,0x92492492,
+    0x24924924,0x49249249,0x92492492,0x24924924,
+    0x49249249,0x92492492,0x24924924,0x49249249,
+    0x92492492,0x24924924,0x49249249,0x92492492,
+    0x24924924,0x49249249,0x92492492,0x24924924,
+    0x49249249,0x92492492,0x24924924,
+]);
+
+class Lev1TParametricDescription extends ParametricDescription {
+    /**
+     * @param {number} absState
+     * @param {number} position
+     * @param {number} vector
+     * @returns {number}
+    */
+    transition(absState, position, vector) {
+        let state = Math.floor(absState / (this.w + 1));
+        let offset = absState % (this.w + 1);
+
+        if (position === this.w) {
+            if (state < 2) { // eslint-disable-line no-lonely-if
+                const loc = Math.imul(vector, 2) + state;
+                offset += this.unpack(this.offsetIncrs0, loc, 1);
+                state = this.unpack(this.toStates0, loc, 2) - 1;
+            }
+        } else if (position === this.w - 1) {
+            if (state < 3) { // eslint-disable-line no-lonely-if
+                const loc = Math.imul(vector, 3) + state;
+                offset += this.unpack(this.offsetIncrs1, loc, 1);
+                state = this.unpack(this.toStates1, loc, 2) - 1;
+            }
+        } else if (position === this.w - 2) {
+            if (state < 6) { // eslint-disable-line no-lonely-if
+                const loc = Math.imul(vector, 6) + state;
+                offset += this.unpack(this.offsetIncrs2, loc, 2);
+                state = this.unpack(this.toStates2, loc, 3) - 1;
+            }
+        } else {
+            if (state < 6) { // eslint-disable-line no-lonely-if
+                const loc = Math.imul(vector, 6) + state;
+                offset += this.unpack(this.offsetIncrs3, loc, 2);
+                state = this.unpack(this.toStates3, loc, 3) - 1;
+            }
+        }
+
+        if (state === -1) {
+            // null state
+            return -1;
+        } else {
+            // translate back to abs
+            return Math.imul(state, this.w + 1) + offset;
+        }
+    }
+
+    // state map
+    //   0 -> [(0, 0)]
+    //   1 -> [(0, 1)]
+    //   2 -> [(0, 1), (1, 1)]
+    //   3 -> [(0, 1), (1, 1), (2, 1)]
+    //   4 -> [(0, 1), (2, 1)]
+    //   5 -> [t(0, 1), (0, 1), (1, 1), (2, 1)]
+
+
+    /** @param {number} w - length of word being checked */
+    constructor(w) {
+        super(w, 1, new Int32Array([0,1,0,-1,-1,-1]));
+    }
+}
+
+Lev1TParametricDescription.prototype.toStates0 = /*2 bits per value */ new Int32Array([
+    0x2,
+]);
+Lev1TParametricDescription.prototype.offsetIncrs0 = /*1 bits per value */ new Int32Array([
+    0x0,
+]);
+
+Lev1TParametricDescription.prototype.toStates1 = /*2 bits per value */ new Int32Array([
+    0xa43,
+]);
+Lev1TParametricDescription.prototype.offsetIncrs1 = /*1 bits per value */ new Int32Array([
+    0x38,
+]);
+
+Lev1TParametricDescription.prototype.toStates2 = /*3 bits per value */ new Int32Array([
+    0x12180003,0xb45a4914,0x69,
+]);
+Lev1TParametricDescription.prototype.offsetIncrs2 = /*2 bits per value */ new Int32Array([
+    0x558a0000,0x5555,
+]);
+
+Lev1TParametricDescription.prototype.toStates3 = /*3 bits per value */ new Int32Array([
+    0x900c0003,0xa1904864,0x45a49169,0x5a6d196a,
+    0x9634,
+]);
+Lev1TParametricDescription.prototype.offsetIncrs3 = /*2 bits per value */ new Int32Array([
+    0xa0fc0000,0x5555ba08,0x55555555,
+]);
diff --git a/tests/rustdoc-js-std/deduplication.js b/tests/rustdoc-js-std/deduplication.js
index 51279dd5ed4..95049d0a174 100644
--- a/tests/rustdoc-js-std/deduplication.js
+++ b/tests/rustdoc-js-std/deduplication.js
@@ -5,6 +5,5 @@ const EXPECTED = {
     'others': [
         { 'path': 'std::f32', 'name': 'is_nan' },
         { 'path': 'std::f64', 'name': 'is_nan' },
-        { 'path': 'std::option::Option', 'name': 'is_none' },
     ],
 };
diff --git a/tests/rustdoc-js-std/path-maxeditdistance.js b/tests/rustdoc-js-std/path-maxeditdistance.js
index 632df658f75..af71713f055 100644
--- a/tests/rustdoc-js-std/path-maxeditdistance.js
+++ b/tests/rustdoc-js-std/path-maxeditdistance.js
@@ -3,16 +3,8 @@ const FILTER_CRATE = "std";
 const EXPECTED = [
     {
         query: 'vec::intoiterator',
-        others: [
-            // trait std::iter::IntoIterator is not the first result
-            { 'path': 'std::vec', 'name': 'IntoIter' },
-            { 'path': 'std::vec::Vec', 'name': 'into_iter' },
-            { 'path': 'std::vec::Drain', 'name': 'into_iter' },
-            { 'path': 'std::vec::IntoIter', 'name': 'into_iter' },
-            { 'path': 'std::vec::ExtractIf', 'name': 'into_iter' },
-            { 'path': 'std::vec::Splice', 'name': 'into_iter' },
-            { 'path': 'std::collections::vec_deque::VecDeque', 'name': 'into_iter' },
-        ],
+        // trait std::iter::IntoIterator is not the first result
+        others: [],
     },
     {
         query: 'vec::iter',
diff --git a/tests/rustdoc-js/non-english-identifier.js b/tests/rustdoc-js/non-english-identifier.js
index 6a2c4cc637c..f2180b4c755 100644
--- a/tests/rustdoc-js/non-english-identifier.js
+++ b/tests/rustdoc-js/non-english-identifier.js
@@ -133,22 +133,34 @@ const EXPECTED = [
                 path: "non_english_identifier",
                 href: "../non_english_identifier/trait.加法.html",
                 desc: "Add"
-            },
+            }],
+        in_args: [{
+            name: "加上",
+            path: "non_english_identifier::加法",
+            href: "../non_english_identifier/trait.加法.html#tymethod.加上",
+        }],
+        returned: [],
+    },
+    { // levensthein and substring checking only kick in at three characters
+        query: '加法宏',
+        others: [
             {
                 name: "中文名称的加法宏",
                 path: "non_english_identifier",
                 href: "../non_english_identifier/macro.中文名称的加法宏.html",
-            },
+            }],
+        in_args: [],
+        returned: [],
+    },
+    { // levensthein and substring checking only kick in at three characters
+        query: '加法A',
+        others: [
             {
                 name: "中文名称的加法API",
                 path: "non_english_identifier",
                 href: "../non_english_identifier/fn.中文名称的加法API.html",
             }],
-        in_args: [{
-            name: "加上",
-            path: "non_english_identifier::加法",
-            href: "../non_english_identifier/trait.加法.html#tymethod.加上",
-        }],
+        in_args: [],
         returned: [],
     },
     { // Extensive type-based search is still buggy, experimental & work-in-progress.
diff --git a/tests/rustdoc-js/path-maxeditdistance.js b/tests/rustdoc-js/path-maxeditdistance.js
index 73b24a6dddf..cf700193ac4 100644
--- a/tests/rustdoc-js/path-maxeditdistance.js
+++ b/tests/rustdoc-js/path-maxeditdistance.js
@@ -14,21 +14,38 @@ const EXPECTED = [
         ],
     },
     {
-        // swap br/rb; that's edit distance 2, where maxPathEditDistance = 3 (11 / 3)
+        // swap br/rb; that's edit distance 1, where maxPathEditDistance = 2
         'query': 'arbacadarba::hocuspocusprestidigitation',
         'others': [
             { 'path': 'abracadabra', 'name': 'HocusPocusPrestidigitation' },
         ],
     },
     {
-        // truncate 5 chars, where maxEditDistance = 7 (21 / 3)
-        'query': 'abracadarba::hocusprestidigitation',
+        // swap p/o o/p, that's also edit distance 1
+        'query': 'abracadabra::hocusopcusprestidigitation',
         'others': [
             { 'path': 'abracadabra', 'name': 'HocusPocusPrestidigitation' },
         ],
     },
     {
-        // truncate 9 chars, where maxEditDistance = 5 (17 / 3)
+        // swap p/o o/p and gi/ig, that's edit distance 2
+        'query': 'abracadabra::hocusopcusprestidiigtation',
+        'others': [
+            { 'path': 'abracadabra', 'name': 'HocusPocusPrestidigitation' },
+        ],
+    },
+    {
+        // swap p/o o/p, gi/ig, and ti/it, that's edit distance 3 and not shown (we stop at 2)
+        'query': 'abracadabra::hocusopcusprestidiigtaiton',
+        'others': [],
+    },
+    {
+        // truncate 5 chars, where maxEditDistance = 2
+        'query': 'abracadarba::hocusprestidigitation',
+        'others': [],
+    },
+    {
+        // truncate 9 chars, where maxEditDistance = 2
         'query': 'abracadarba::hprestidigitation',
         'others': [],
     },
diff --git a/tests/rustdoc-js/prototype.js b/tests/rustdoc-js/prototype.js
index da72fdce3db..0862acd8ffa 100644
--- a/tests/rustdoc-js/prototype.js
+++ b/tests/rustdoc-js/prototype.js
@@ -9,7 +9,9 @@ const EXPECTED = [
     },
     {
         'query': '__proto__',
-        'others': [],
+        'others': [
+            {"path": "", "name": "prototype"},
+        ],
         'returned': [],
         'in_args': [],
     },
diff --git a/tests/ui/closures/2229_closure_analysis/diagnostics/closure-origin-multi-variant-diagnostics.stderr b/tests/ui/closures/2229_closure_analysis/diagnostics/closure-origin-multi-variant-diagnostics.stderr
index 347fa3fa892..4cfe51eccac 100644
--- a/tests/ui/closures/2229_closure_analysis/diagnostics/closure-origin-multi-variant-diagnostics.stderr
+++ b/tests/ui/closures/2229_closure_analysis/diagnostics/closure-origin-multi-variant-diagnostics.stderr
@@ -11,10 +11,6 @@ note: closure cannot be moved more than once as it is not `Copy` due to moving t
    |
 LL |         if let MultiVariant::Point(ref mut x, _) = point {
    |                                                    ^^^^^
-help: consider mutably borrowing `c`
-   |
-LL |     let a = &mut c;
-   |             ++++
 
 error: aborting due to 1 previous error
 
diff --git a/tests/ui/closures/2229_closure_analysis/diagnostics/closure-origin-single-variant-diagnostics.stderr b/tests/ui/closures/2229_closure_analysis/diagnostics/closure-origin-single-variant-diagnostics.stderr
index c9b27e76879..2ed611d6b52 100644
--- a/tests/ui/closures/2229_closure_analysis/diagnostics/closure-origin-single-variant-diagnostics.stderr
+++ b/tests/ui/closures/2229_closure_analysis/diagnostics/closure-origin-single-variant-diagnostics.stderr
@@ -11,10 +11,6 @@ note: closure cannot be moved more than once as it is not `Copy` due to moving t
    |
 LL |         let SingleVariant::Point(ref mut x, _) = point;
    |                                                  ^^^^^
-help: consider mutably borrowing `c`
-   |
-LL |     let b = &mut c;
-   |             ++++
 
 error: aborting due to 1 previous error
 
diff --git a/tests/ui/closures/2229_closure_analysis/diagnostics/closure-origin-struct-diagnostics.stderr b/tests/ui/closures/2229_closure_analysis/diagnostics/closure-origin-struct-diagnostics.stderr
index 079a9abedf9..47db2c76a2f 100644
--- a/tests/ui/closures/2229_closure_analysis/diagnostics/closure-origin-struct-diagnostics.stderr
+++ b/tests/ui/closures/2229_closure_analysis/diagnostics/closure-origin-struct-diagnostics.stderr
@@ -11,10 +11,6 @@ note: closure cannot be moved more than once as it is not `Copy` due to moving t
    |
 LL |         x.y.a += 1;
    |         ^^^^^
-help: consider mutably borrowing `hello`
-   |
-LL |     let b = &mut hello;
-   |             ++++
 
 error: aborting due to 1 previous error
 
diff --git a/tests/ui/closures/2229_closure_analysis/diagnostics/closure-origin-tuple-diagnostics-1.stderr b/tests/ui/closures/2229_closure_analysis/diagnostics/closure-origin-tuple-diagnostics-1.stderr
index 0bf717404ce..9db64ec04b9 100644
--- a/tests/ui/closures/2229_closure_analysis/diagnostics/closure-origin-tuple-diagnostics-1.stderr
+++ b/tests/ui/closures/2229_closure_analysis/diagnostics/closure-origin-tuple-diagnostics-1.stderr
@@ -11,10 +11,6 @@ note: closure cannot be moved more than once as it is not `Copy` due to moving t
    |
 LL |         x.0 += 1;
    |         ^^^
-help: consider mutably borrowing `hello`
-   |
-LL |     let b = &mut hello;
-   |             ++++
 
 error: aborting due to 1 previous error
 
diff --git a/tests/ui/moves/auxiliary/suggest-borrow-for-generic-arg-aux.rs b/tests/ui/moves/auxiliary/suggest-borrow-for-generic-arg-aux.rs
new file mode 100644
index 00000000000..c71238ba072
--- /dev/null
+++ b/tests/ui/moves/auxiliary/suggest-borrow-for-generic-arg-aux.rs
@@ -0,0 +1,20 @@
+//! auxiliary definitons for suggest-borrow-for-generic-arg.rs, to ensure the suggestion works on
+//! functions defined in other crates.
+
+use std::io::{self, Read, Write};
+use std::iter::Sum;
+
+pub fn write_stuff<W: Write>(mut writer: W) -> io::Result<()> {
+    writeln!(writer, "stuff")
+}
+
+pub fn read_and_discard<R: Read>(mut reader: R) -> io::Result<()> {
+    let mut buf = Vec::new();
+    reader.read_to_end(&mut buf).map(|_| ())
+}
+
+pub fn sum_three<I: IntoIterator>(iter: I) -> <I as IntoIterator>::Item
+    where <I as IntoIterator>::Item: Sum
+{
+    iter.into_iter().take(3).sum()
+}
diff --git a/tests/ui/moves/borrow-closures-instead-of-move.rs b/tests/ui/moves/borrow-closures-instead-of-move.rs
index e4bca54e995..869aa654ef7 100644
--- a/tests/ui/moves/borrow-closures-instead-of-move.rs
+++ b/tests/ui/moves/borrow-closures-instead-of-move.rs
@@ -1,4 +1,4 @@
-fn takes_fn(f: impl Fn()) { //~ HELP if `impl Fn()` implemented `Clone`
+fn takes_fn(f: impl Fn()) {
     loop {
         takes_fnonce(f);
         //~^ ERROR use of moved value
diff --git a/tests/ui/moves/borrow-closures-instead-of-move.stderr b/tests/ui/moves/borrow-closures-instead-of-move.stderr
index ab6ff417efb..ea145f365c2 100644
--- a/tests/ui/moves/borrow-closures-instead-of-move.stderr
+++ b/tests/ui/moves/borrow-closures-instead-of-move.stderr
@@ -8,21 +8,6 @@ LL |     loop {
 LL |         takes_fnonce(f);
    |                      ^ value moved here, in previous iteration of loop
    |
-note: consider changing this parameter type in function `takes_fnonce` to borrow instead if owning the value isn't necessary
-  --> $DIR/borrow-closures-instead-of-move.rs:34:20
-   |
-LL | fn takes_fnonce(_: impl FnOnce()) {}
-   |    ------------    ^^^^^^^^^^^^^ this parameter takes ownership of the value
-   |    |
-   |    in this function
-help: if `impl Fn()` implemented `Clone`, you could clone the value
-  --> $DIR/borrow-closures-instead-of-move.rs:1:16
-   |
-LL | fn takes_fn(f: impl Fn()) {
-   |                ^^^^^^^^^ consider constraining this type parameter with `Clone`
-LL |     loop {
-LL |         takes_fnonce(f);
-   |                      - you could clone this value
 help: consider borrowing `f`
    |
 LL |         takes_fnonce(&f);
@@ -40,13 +25,6 @@ LL |         takes_fnonce(m);
 LL |     takes_fnonce(m);
    |                  ^ value used here after move
    |
-note: consider changing this parameter type in function `takes_fnonce` to borrow instead if owning the value isn't necessary
-  --> $DIR/borrow-closures-instead-of-move.rs:34:20
-   |
-LL | fn takes_fnonce(_: impl FnOnce()) {}
-   |    ------------    ^^^^^^^^^^^^^ this parameter takes ownership of the value
-   |    |
-   |    in this function
 help: if `impl FnMut()` implemented `Clone`, you could clone the value
   --> $DIR/borrow-closures-instead-of-move.rs:9:20
    |
diff --git a/tests/ui/moves/moved-value-on-as-ref-arg.stderr b/tests/ui/moves/moved-value-on-as-ref-arg.stderr
index 4004b7a43bc..a99bdb4fe9d 100644
--- a/tests/ui/moves/moved-value-on-as-ref-arg.stderr
+++ b/tests/ui/moves/moved-value-on-as-ref-arg.stderr
@@ -8,7 +8,7 @@ LL |     foo(bar);
 LL |     let _baa = bar;
    |                ^^^ value used here after move
    |
-help: borrow the value to avoid moving it
+help: consider borrowing `bar`
    |
 LL |     foo(&bar);
    |         +
@@ -31,7 +31,7 @@ LL | struct Bar;
 ...
 LL |     qux(bar);
    |         --- you could clone this value
-help: borrow the value to avoid moving it
+help: consider mutably borrowing `bar`
    |
 LL |     qux(&mut bar);
    |         ++++
@@ -46,7 +46,7 @@ LL |     bat(bar);
 LL |     let _baa = bar;
    |                ^^^ value used here after move
    |
-help: borrow the value to avoid moving it
+help: consider borrowing `bar`
    |
 LL |     bat(&bar);
    |         +
@@ -69,7 +69,7 @@ LL | struct Bar;
 ...
 LL |     baz(bar);
    |         --- you could clone this value
-help: borrow the value to avoid moving it
+help: consider mutably borrowing `bar`
    |
 LL |     baz(&mut bar);
    |         ++++
diff --git a/tests/ui/moves/moves-based-on-type-no-recursive-stack-closure.stderr b/tests/ui/moves/moves-based-on-type-no-recursive-stack-closure.stderr
index a8473bb8198..a4c8401ce57 100644
--- a/tests/ui/moves/moves-based-on-type-no-recursive-stack-closure.stderr
+++ b/tests/ui/moves/moves-based-on-type-no-recursive-stack-closure.stderr
@@ -24,10 +24,6 @@ LL | fn conspirator<F>(mut f: F) where F: FnMut(&mut R, bool) {
    |                ^ consider constraining this type parameter with `Clone`
 LL |     let mut r = R {c: Box::new(f)};
    |                                - you could clone this value
-help: consider mutably borrowing `f`
-   |
-LL |     let mut r = R {c: Box::new(&mut f)};
-   |                                ++++
 
 error: aborting due to 2 previous errors
 
diff --git a/tests/ui/moves/suggest-borrow-for-generic-arg.fixed b/tests/ui/moves/suggest-borrow-for-generic-arg.fixed
new file mode 100644
index 00000000000..b5e0b468aa6
--- /dev/null
+++ b/tests/ui/moves/suggest-borrow-for-generic-arg.fixed
@@ -0,0 +1,46 @@
+//! Test suggetions to borrow generic arguments instead of moving. Tests for other instances of this
+//! can be found in `moved-value-on-as-ref-arg.rs` and `borrow-closures-instead-of-move.rs`
+//@ run-rustfix
+//@ aux-crate:aux=suggest-borrow-for-generic-arg-aux.rs
+//@ edition: 2021
+
+#![allow(unused_mut)]
+use std::io::{self, Write};
+
+// test for `std::io::Write` (#131413)
+fn test_write() -> io::Result<()> {
+    let mut stdout = io::stdout();
+    aux::write_stuff(&stdout)?; //~ HELP consider borrowing `stdout`
+    writeln!(stdout, "second line")?; //~ ERROR borrow of moved value: `stdout`
+
+    let mut buf = Vec::new();
+    aux::write_stuff(&mut buf.clone())?; //~ HELP consider mutably borrowing `buf`
+    //~^ HELP consider cloning the value
+    writeln!(buf, "second_line") //~ ERROR borrow of moved value: `buf`
+}
+
+/// test for `std::io::Read` (#131413)
+fn test_read() -> io::Result<()> {
+    let stdin = io::stdin();
+    aux::read_and_discard(&stdin)?; //~ HELP consider borrowing `stdin`
+    aux::read_and_discard(stdin)?; //~ ERROR use of moved value: `stdin`
+
+    let mut bytes = std::collections::VecDeque::from([1, 2, 3, 4, 5, 6]);
+    aux::read_and_discard(&mut bytes.clone())?; //~ HELP consider mutably borrowing `bytes`
+    //~^ HELP consider cloning the value
+    aux::read_and_discard(bytes) //~ ERROR use of moved value: `bytes`
+}
+
+/// test that suggestions work with projection types in the callee's signature
+fn test_projections() {
+    let mut iter = [1, 2, 3, 4, 5, 6].into_iter();
+    let _six: usize = aux::sum_three(&mut iter.clone()); //~ HELP consider mutably borrowing `iter`
+    //~^ HELP consider cloning the value
+    let _fifteen: usize = aux::sum_three(iter); //~ ERROR use of moved value: `iter`
+}
+
+fn main() {
+    test_write().unwrap();
+    test_read().unwrap();
+    test_projections();
+}
diff --git a/tests/ui/moves/suggest-borrow-for-generic-arg.rs b/tests/ui/moves/suggest-borrow-for-generic-arg.rs
new file mode 100644
index 00000000000..e08978db63a
--- /dev/null
+++ b/tests/ui/moves/suggest-borrow-for-generic-arg.rs
@@ -0,0 +1,46 @@
+//! Test suggetions to borrow generic arguments instead of moving. Tests for other instances of this
+//! can be found in `moved-value-on-as-ref-arg.rs` and `borrow-closures-instead-of-move.rs`
+//@ run-rustfix
+//@ aux-crate:aux=suggest-borrow-for-generic-arg-aux.rs
+//@ edition: 2021
+
+#![allow(unused_mut)]
+use std::io::{self, Write};
+
+// test for `std::io::Write` (#131413)
+fn test_write() -> io::Result<()> {
+    let mut stdout = io::stdout();
+    aux::write_stuff(stdout)?; //~ HELP consider borrowing `stdout`
+    writeln!(stdout, "second line")?; //~ ERROR borrow of moved value: `stdout`
+
+    let mut buf = Vec::new();
+    aux::write_stuff(buf)?; //~ HELP consider mutably borrowing `buf`
+    //~^ HELP consider cloning the value
+    writeln!(buf, "second_line") //~ ERROR borrow of moved value: `buf`
+}
+
+/// test for `std::io::Read` (#131413)
+fn test_read() -> io::Result<()> {
+    let stdin = io::stdin();
+    aux::read_and_discard(stdin)?; //~ HELP consider borrowing `stdin`
+    aux::read_and_discard(stdin)?; //~ ERROR use of moved value: `stdin`
+
+    let mut bytes = std::collections::VecDeque::from([1, 2, 3, 4, 5, 6]);
+    aux::read_and_discard(bytes)?; //~ HELP consider mutably borrowing `bytes`
+    //~^ HELP consider cloning the value
+    aux::read_and_discard(bytes) //~ ERROR use of moved value: `bytes`
+}
+
+/// test that suggestions work with projection types in the callee's signature
+fn test_projections() {
+    let mut iter = [1, 2, 3, 4, 5, 6].into_iter();
+    let _six: usize = aux::sum_three(iter); //~ HELP consider mutably borrowing `iter`
+    //~^ HELP consider cloning the value
+    let _fifteen: usize = aux::sum_three(iter); //~ ERROR use of moved value: `iter`
+}
+
+fn main() {
+    test_write().unwrap();
+    test_read().unwrap();
+    test_projections();
+}
diff --git a/tests/ui/moves/suggest-borrow-for-generic-arg.stderr b/tests/ui/moves/suggest-borrow-for-generic-arg.stderr
new file mode 100644
index 00000000000..07e24f566cb
--- /dev/null
+++ b/tests/ui/moves/suggest-borrow-for-generic-arg.stderr
@@ -0,0 +1,93 @@
+error[E0382]: borrow of moved value: `stdout`
+  --> $DIR/suggest-borrow-for-generic-arg.rs:14:14
+   |
+LL |     let mut stdout = io::stdout();
+   |         ---------- move occurs because `stdout` has type `Stdout`, which does not implement the `Copy` trait
+LL |     aux::write_stuff(stdout)?;
+   |                      ------ value moved here
+LL |     writeln!(stdout, "second line")?;
+   |              ^^^^^^ value borrowed here after move
+   |
+help: consider borrowing `stdout`
+   |
+LL |     aux::write_stuff(&stdout)?;
+   |                      +
+
+error[E0382]: borrow of moved value: `buf`
+  --> $DIR/suggest-borrow-for-generic-arg.rs:19:14
+   |
+LL |     let mut buf = Vec::new();
+   |         ------- move occurs because `buf` has type `Vec<u8>`, which does not implement the `Copy` trait
+LL |     aux::write_stuff(buf)?;
+   |                      --- value moved here
+LL |
+LL |     writeln!(buf, "second_line")
+   |              ^^^ value borrowed here after move
+   |
+help: consider mutably borrowing `buf`
+   |
+LL |     aux::write_stuff(&mut buf)?;
+   |                      ++++
+help: consider cloning the value if the performance cost is acceptable
+   |
+LL |     aux::write_stuff(buf.clone())?;
+   |                         ++++++++
+
+error[E0382]: use of moved value: `stdin`
+  --> $DIR/suggest-borrow-for-generic-arg.rs:26:27
+   |
+LL |     let stdin = io::stdin();
+   |         ----- move occurs because `stdin` has type `Stdin`, which does not implement the `Copy` trait
+LL |     aux::read_and_discard(stdin)?;
+   |                           ----- value moved here
+LL |     aux::read_and_discard(stdin)?;
+   |                           ^^^^^ value used here after move
+   |
+help: consider borrowing `stdin`
+   |
+LL |     aux::read_and_discard(&stdin)?;
+   |                           +
+
+error[E0382]: use of moved value: `bytes`
+  --> $DIR/suggest-borrow-for-generic-arg.rs:31:27
+   |
+LL |     let mut bytes = std::collections::VecDeque::from([1, 2, 3, 4, 5, 6]);
+   |         --------- move occurs because `bytes` has type `VecDeque<u8>`, which does not implement the `Copy` trait
+LL |     aux::read_and_discard(bytes)?;
+   |                           ----- value moved here
+LL |
+LL |     aux::read_and_discard(bytes)
+   |                           ^^^^^ value used here after move
+   |
+help: consider mutably borrowing `bytes`
+   |
+LL |     aux::read_and_discard(&mut bytes)?;
+   |                           ++++
+help: consider cloning the value if the performance cost is acceptable
+   |
+LL |     aux::read_and_discard(bytes.clone())?;
+   |                                ++++++++
+
+error[E0382]: use of moved value: `iter`
+  --> $DIR/suggest-borrow-for-generic-arg.rs:39:42
+   |
+LL |     let mut iter = [1, 2, 3, 4, 5, 6].into_iter();
+   |         -------- move occurs because `iter` has type `std::array::IntoIter<usize, 6>`, which does not implement the `Copy` trait
+LL |     let _six: usize = aux::sum_three(iter);
+   |                                      ---- value moved here
+LL |
+LL |     let _fifteen: usize = aux::sum_three(iter);
+   |                                          ^^^^ value used here after move
+   |
+help: consider mutably borrowing `iter`
+   |
+LL |     let _six: usize = aux::sum_three(&mut iter);
+   |                                      ++++
+help: consider cloning the value if the performance cost is acceptable
+   |
+LL |     let _six: usize = aux::sum_three(iter.clone());
+   |                                          ++++++++
+
+error: aborting due to 5 previous errors
+
+For more information about this error, try `rustc --explain E0382`.
diff --git a/tests/ui/not-copy-closure.stderr b/tests/ui/not-copy-closure.stderr
index 50e25a24d81..60cb1352313 100644
--- a/tests/ui/not-copy-closure.stderr
+++ b/tests/ui/not-copy-closure.stderr
@@ -11,10 +11,6 @@ note: closure cannot be moved more than once as it is not `Copy` due to moving t
    |
 LL |         a += 1;
    |         ^
-help: consider mutably borrowing `hello`
-   |
-LL |     let b = &mut hello;
-   |             ++++
 
 error: aborting due to 1 previous error
 
diff --git a/triagebot.toml b/triagebot.toml
index 0ac971d48d8..d5bc549ca9f 100644
--- a/triagebot.toml
+++ b/triagebot.toml
@@ -977,6 +977,7 @@ contributing_url = "https://rustc-dev-guide.rust-lang.org/getting-started.html"
 users_on_vacation = [
     "jyn514",
     "oli-obk",
+    "onur-ozkan",
 ]
 
 [assign.adhoc_groups]