about summary refs log tree commit diff
diff options
context:
space:
mode:
authorbors <bors@rust-lang.org>2022-10-23 11:33:18 +0000
committerbors <bors@rust-lang.org>2022-10-23 11:33:18 +0000
commit9be2f35a4c1ed1b04aa4a6945b64763f599259ff (patch)
tree0fcc4f7182b13a4711e19ecf0262767633c6b862
parente64f1110c062f61746f222059439529a43ccf6dc (diff)
parentd35a24a0c203e7230d128230d28d8fbababebe8d (diff)
downloadrust-9be2f35a4c1ed1b04aa4a6945b64763f599259ff.tar.gz
rust-9be2f35a4c1ed1b04aa4a6945b64763f599259ff.zip
Auto merge of #103431 - Dylan-DPC:rollup-oozfo89, r=Dylan-DPC
Rollup of 6 pull requests

Successful merges:

 - #101293 (Recover when unclosed char literal is parsed as a lifetime in some positions)
 - #101908 (Suggest let for assignment, and some code refactor)
 - #103192 (rustdoc: Eliminate uses of `EarlyDocLinkResolver::all_traits`)
 - #103226 (Check `needs_infer` before `needs_drop` during HIR generator analysis)
 - #103249 (resolve: Revert "Set effective visibilities for imports more precisely")
 - #103305 (Move some tests to more reasonable places)

Failed merges:

r? `@ghost`
`@rustbot` modify labels: rollup
-rw-r--r--compiler/rustc_errors/src/lib.rs3
-rw-r--r--compiler/rustc_hir_typeck/src/generator_interior/drop_ranges/record_consumed_borrow.rs17
-rw-r--r--compiler/rustc_hir_typeck/src/generator_interior/mod.rs24
-rw-r--r--compiler/rustc_metadata/src/rmeta/decoder/cstore_impl.rs5
-rw-r--r--compiler/rustc_parse/src/lexer/mod.rs9
-rw-r--r--compiler/rustc_parse/src/parser/expr.rs76
-rw-r--r--compiler/rustc_parse/src/parser/pat.rs20
-rw-r--r--compiler/rustc_resolve/src/access_levels.rs26
-rw-r--r--compiler/rustc_resolve/src/imports.rs27
-rw-r--r--compiler/rustc_resolve/src/late.rs8
-rw-r--r--compiler/rustc_resolve/src/late/diagnostics.rs96
-rw-r--r--compiler/rustc_span/src/source_map.rs20
-rw-r--r--src/librustdoc/clean/blanket_impl.rs196
-rw-r--r--src/librustdoc/core.rs17
-rw-r--r--src/librustdoc/passes/collect_intra_doc_links/early.rs32
-rw-r--r--src/test/ui/borrowck/issue-23338-params-outlive-temps-of-body.rs (renamed from src/test/ui/issues/issue-23338-params-outlive-temps-of-body.rs)0
-rw-r--r--src/test/ui/drop/issue-23338-ensure-param-drop-order.rs (renamed from src/test/ui/issues/issue-23338-ensure-param-drop-order.rs)0
-rw-r--r--src/test/ui/dropck/issue-24805-dropck-itemless.rs (renamed from src/test/ui/issues/issue-24805-dropck-itemless.rs)0
-rw-r--r--src/test/ui/inference/issue-36053.rs (renamed from src/test/ui/issues/issue-36053.rs)0
-rw-r--r--src/test/ui/parser/issues/issue-93282.rs1
-rw-r--r--src/test/ui/parser/issues/issue-93282.stderr27
-rw-r--r--src/test/ui/parser/label-is-actually-char.rs16
-rw-r--r--src/test/ui/parser/label-is-actually-char.stderr46
-rw-r--r--src/test/ui/parser/numeric-lifetime.stderr16
-rw-r--r--src/test/ui/parser/require-parens-for-chained-comparison.rs2
-rw-r--r--src/test/ui/parser/require-parens-for-chained-comparison.stderr16
-rw-r--r--src/test/ui/privacy/access_levels.rs1
-rw-r--r--src/test/ui/privacy/access_levels.stderr8
-rw-r--r--src/test/ui/return/issue-64620.rs (renamed from src/test/ui/issues/issue-64620.rs)0
-rw-r--r--src/test/ui/return/issue-64620.stderr (renamed from src/test/ui/issues/issue-64620.stderr)0
-rw-r--r--src/test/ui/structs-enums/issue-2718-a.rs (renamed from src/test/ui/issues/issue-2718-a.rs)0
-rw-r--r--src/test/ui/structs-enums/issue-2718-a.stderr (renamed from src/test/ui/issues/issue-2718-a.stderr)0
-rw-r--r--src/test/ui/suggestions/suggest-let-for-assignment.fixed17
-rw-r--r--src/test/ui/suggestions/suggest-let-for-assignment.rs17
-rw-r--r--src/test/ui/suggestions/suggest-let-for-assignment.stderr60
-rw-r--r--src/tools/tidy/src/ui_tests.rs2
36 files changed, 541 insertions, 264 deletions
diff --git a/compiler/rustc_errors/src/lib.rs b/compiler/rustc_errors/src/lib.rs
index 9fafbe4bd40..0963ea71f80 100644
--- a/compiler/rustc_errors/src/lib.rs
+++ b/compiler/rustc_errors/src/lib.rs
@@ -463,6 +463,9 @@ pub enum StashKey {
     UnderscoreForArrayLengths,
     EarlySyntaxWarning,
     CallIntoMethod,
+    /// When an invalid lifetime e.g. `'2` should be reinterpreted
+    /// as a char literal in the parser
+    LifetimeIsChar,
 }
 
 fn default_track_diagnostic(_: &Diagnostic) {}
diff --git a/compiler/rustc_hir_typeck/src/generator_interior/drop_ranges/record_consumed_borrow.rs b/compiler/rustc_hir_typeck/src/generator_interior/drop_ranges/record_consumed_borrow.rs
index 7954ddc4166..bfe95852aa7 100644
--- a/compiler/rustc_hir_typeck/src/generator_interior/drop_ranges/record_consumed_borrow.rs
+++ b/compiler/rustc_hir_typeck/src/generator_interior/drop_ranges/record_consumed_borrow.rs
@@ -6,8 +6,11 @@ use crate::{
 use hir::{def_id::DefId, Body, HirId, HirIdMap};
 use rustc_data_structures::fx::FxHashSet;
 use rustc_hir as hir;
-use rustc_middle::hir::place::{PlaceBase, Projection, ProjectionKind};
 use rustc_middle::ty::{ParamEnv, TyCtxt};
+use rustc_middle::{
+    hir::place::{PlaceBase, Projection, ProjectionKind},
+    ty::TypeVisitable,
+};
 
 pub(super) fn find_consumed_and_borrowed<'a, 'tcx>(
     fcx: &'a FnCtxt<'a, 'tcx>,
@@ -198,11 +201,13 @@ impl<'tcx> expr_use_visitor::Delegate<'tcx> for ExprUseDelegate<'tcx> {
 
         // If the type being assigned needs dropped, then the mutation counts as a borrow
         // since it is essentially doing `Drop::drop(&mut x); x = new_value;`.
-        //
-        // FIXME(drop-tracking): We need to be more responsible about inference
-        // variables here, since `needs_drop` is a "raw" type query, i.e. it
-        // basically requires types to have been fully resolved.
-        if assignee_place.place.base_ty.needs_drop(self.tcx, self.param_env) {
+        let ty = self.tcx.erase_regions(assignee_place.place.base_ty);
+        if ty.needs_infer() {
+            self.tcx.sess.delay_span_bug(
+                self.tcx.hir().span(assignee_place.hir_id),
+                &format!("inference variables in {ty}"),
+            );
+        } else if ty.needs_drop(self.tcx, self.param_env) {
             self.places
                 .borrowed
                 .insert(TrackedValue::from_place_with_projections_allowed(assignee_place));
diff --git a/compiler/rustc_hir_typeck/src/generator_interior/mod.rs b/compiler/rustc_hir_typeck/src/generator_interior/mod.rs
index 898419b5b23..b7dd599cd43 100644
--- a/compiler/rustc_hir_typeck/src/generator_interior/mod.rs
+++ b/compiler/rustc_hir_typeck/src/generator_interior/mod.rs
@@ -377,15 +377,6 @@ impl<'a, 'tcx> Visitor<'tcx> for InteriorVisitor<'a, 'tcx> {
         debug!("is_borrowed_temporary: {:?}", self.drop_ranges.is_borrowed_temporary(expr));
 
         let ty = self.fcx.typeck_results.borrow().expr_ty_adjusted_opt(expr);
-        let may_need_drop = |ty: Ty<'tcx>| {
-            // Avoid ICEs in needs_drop.
-            let ty = self.fcx.resolve_vars_if_possible(ty);
-            let ty = self.fcx.tcx.erase_regions(ty);
-            if ty.needs_infer() {
-                return true;
-            }
-            ty.needs_drop(self.fcx.tcx, self.fcx.param_env)
-        };
 
         // Typically, the value produced by an expression is consumed by its parent in some way,
         // so we only have to check if the parent contains a yield (note that the parent may, for
@@ -403,9 +394,18 @@ impl<'a, 'tcx> Visitor<'tcx> for InteriorVisitor<'a, 'tcx> {
         // src/test/ui/generator/drop-tracking-parent-expression.rs.
         let scope = if self.drop_ranges.is_borrowed_temporary(expr)
             || ty.map_or(true, |ty| {
-                let needs_drop = may_need_drop(ty);
-                debug!(?needs_drop, ?ty);
-                needs_drop
+                // Avoid ICEs in needs_drop.
+                let ty = self.fcx.resolve_vars_if_possible(ty);
+                let ty = self.fcx.tcx.erase_regions(ty);
+                if ty.needs_infer() {
+                    self.fcx
+                        .tcx
+                        .sess
+                        .delay_span_bug(expr.span, &format!("inference variables in {ty}"));
+                    true
+                } else {
+                    ty.needs_drop(self.fcx.tcx, self.fcx.param_env)
+                }
             }) {
             self.rvalue_scopes.temporary_scope(self.region_scope_tree, expr.hir_id.local_id)
         } else {
diff --git a/compiler/rustc_metadata/src/rmeta/decoder/cstore_impl.rs b/compiler/rustc_metadata/src/rmeta/decoder/cstore_impl.rs
index 41542c3283d..a0a0855251b 100644
--- a/compiler/rustc_metadata/src/rmeta/decoder/cstore_impl.rs
+++ b/compiler/rustc_metadata/src/rmeta/decoder/cstore_impl.rs
@@ -587,11 +587,6 @@ impl CStore {
         self.get_crate_data(cnum).get_proc_macro_quoted_span(id, sess)
     }
 
-    /// Decodes all traits in the crate (for rustdoc).
-    pub fn traits_in_crate_untracked(&self, cnum: CrateNum) -> impl Iterator<Item = DefId> + '_ {
-        self.get_crate_data(cnum).get_traits()
-    }
-
     /// Decodes all trait impls in the crate (for rustdoc).
     pub fn trait_impls_in_crate_untracked(
         &self,
diff --git a/compiler/rustc_parse/src/lexer/mod.rs b/compiler/rustc_parse/src/lexer/mod.rs
index 88540e13ef2..462bce16ad7 100644
--- a/compiler/rustc_parse/src/lexer/mod.rs
+++ b/compiler/rustc_parse/src/lexer/mod.rs
@@ -3,7 +3,9 @@ use rustc_ast::ast::{self, AttrStyle};
 use rustc_ast::token::{self, CommentKind, Delimiter, Token, TokenKind};
 use rustc_ast::tokenstream::TokenStream;
 use rustc_ast::util::unicode::contains_text_flow_control_chars;
-use rustc_errors::{error_code, Applicability, DiagnosticBuilder, ErrorGuaranteed, PResult};
+use rustc_errors::{
+    error_code, Applicability, DiagnosticBuilder, ErrorGuaranteed, PResult, StashKey,
+};
 use rustc_lexer::unescape::{self, Mode};
 use rustc_lexer::Cursor;
 use rustc_lexer::{Base, DocStyle, RawStrError};
@@ -203,7 +205,10 @@ impl<'a> StringReader<'a> {
                     // this is necessary.
                     let lifetime_name = self.str_from(start);
                     if starts_with_number {
-                        self.err_span_(start, self.pos, "lifetimes cannot start with a number");
+                        let span = self.mk_sp(start, self.pos);
+                        let mut diag = self.sess.struct_err("lifetimes cannot start with a number");
+                        diag.set_span(span);
+                        diag.stash(span, StashKey::LifetimeIsChar);
                     }
                     let ident = Symbol::intern(lifetime_name);
                     token::Lifetime(ident)
diff --git a/compiler/rustc_parse/src/parser/expr.rs b/compiler/rustc_parse/src/parser/expr.rs
index 5b466cec8e1..98520a446a6 100644
--- a/compiler/rustc_parse/src/parser/expr.rs
+++ b/compiler/rustc_parse/src/parser/expr.rs
@@ -42,8 +42,10 @@ use rustc_ast::{AnonConst, BinOp, BinOpKind, FnDecl, FnRetTy, MacCall, Param, Ty
 use rustc_ast::{Arm, Async, BlockCheckMode, Expr, ExprKind, Label, Movability, RangeLimits};
 use rustc_ast::{ClosureBinder, StmtKind};
 use rustc_ast_pretty::pprust;
-use rustc_errors::IntoDiagnostic;
-use rustc_errors::{Applicability, Diagnostic, PResult};
+use rustc_errors::{
+    Applicability, Diagnostic, DiagnosticBuilder, ErrorGuaranteed, IntoDiagnostic, PResult,
+    StashKey,
+};
 use rustc_session::errors::ExprParenthesesNeeded;
 use rustc_session::lint::builtin::BREAK_WITH_LABEL_AND_LOOP;
 use rustc_session::lint::BuiltinLintDiagnostics;
@@ -1513,11 +1515,11 @@ impl<'a> Parser<'a> {
     /// Parse `'label: $expr`. The label is already parsed.
     fn parse_labeled_expr(
         &mut self,
-        label: Label,
+        label_: Label,
         mut consume_colon: bool,
     ) -> PResult<'a, P<Expr>> {
-        let lo = label.ident.span;
-        let label = Some(label);
+        let lo = label_.ident.span;
+        let label = Some(label_);
         let ate_colon = self.eat(&token::Colon);
         let expr = if self.eat_keyword(kw::While) {
             self.parse_while_expr(label, lo)
@@ -1530,6 +1532,19 @@ impl<'a> Parser<'a> {
         {
             self.parse_block_expr(label, lo, BlockCheckMode::Default)
         } else if !ate_colon
+            && (matches!(self.token.kind, token::CloseDelim(_) | token::Comma)
+                || self.token.is_op())
+        {
+            let lit = self.recover_unclosed_char(label_.ident, |self_| {
+                self_.sess.create_err(UnexpectedTokenAfterLabel {
+                    span: self_.token.span,
+                    remove_label: None,
+                    enclose_in_block: None,
+                })
+            });
+            consume_colon = false;
+            Ok(self.mk_expr(lo, ExprKind::Lit(lit)))
+        } else if !ate_colon
             && (self.check_noexpect(&TokenKind::Comma) || self.check_noexpect(&TokenKind::Gt))
         {
             // We're probably inside of a `Path<'a>` that needs a turbofish
@@ -1603,6 +1618,39 @@ impl<'a> Parser<'a> {
         Ok(expr)
     }
 
+    /// Emit an error when a char is parsed as a lifetime because of a missing quote
+    pub(super) fn recover_unclosed_char(
+        &mut self,
+        lifetime: Ident,
+        err: impl FnOnce(&mut Self) -> DiagnosticBuilder<'a, ErrorGuaranteed>,
+    ) -> ast::Lit {
+        if let Some(mut diag) =
+            self.sess.span_diagnostic.steal_diagnostic(lifetime.span, StashKey::LifetimeIsChar)
+        {
+            diag.span_suggestion_verbose(
+                lifetime.span.shrink_to_hi(),
+                "add `'` to close the char literal",
+                "'",
+                Applicability::MaybeIncorrect,
+            )
+            .emit();
+        } else {
+            err(self)
+                .span_suggestion_verbose(
+                    lifetime.span.shrink_to_hi(),
+                    "add `'` to close the char literal",
+                    "'",
+                    Applicability::MaybeIncorrect,
+                )
+                .emit();
+        }
+        ast::Lit {
+            token_lit: token::Lit::new(token::LitKind::Char, lifetime.name, None),
+            kind: ast::LitKind::Char(lifetime.name.as_str().chars().next().unwrap_or('_')),
+            span: lifetime.span,
+        }
+    }
+
     /// Recover on the syntax `do catch { ... }` suggesting `try { ... }` instead.
     fn recover_do_catch(&mut self) -> PResult<'a, P<Expr>> {
         let lo = self.token.span;
@@ -1728,7 +1776,7 @@ impl<'a> Parser<'a> {
     }
 
     pub(super) fn parse_lit(&mut self) -> PResult<'a, Lit> {
-        self.parse_opt_lit().ok_or_else(|| {
+        self.parse_opt_lit().ok_or(()).or_else(|()| {
             if let token::Interpolated(inner) = &self.token.kind {
                 let expr = match inner.as_ref() {
                     token::NtExpr(expr) => Some(expr),
@@ -1740,12 +1788,22 @@ impl<'a> Parser<'a> {
                         let mut err = InvalidInterpolatedExpression { span: self.token.span }
                             .into_diagnostic(&self.sess.span_diagnostic);
                         err.downgrade_to_delayed_bug();
-                        return err;
+                        return Err(err);
                     }
                 }
             }
-            let msg = format!("unexpected token: {}", super::token_descr(&self.token));
-            self.struct_span_err(self.token.span, &msg)
+            let token = self.token.clone();
+            let err = |self_: &mut Self| {
+                let msg = format!("unexpected token: {}", super::token_descr(&token));
+                self_.struct_span_err(token.span, &msg)
+            };
+            // On an error path, eagerly consider a lifetime to be an unclosed character lit
+            if self.token.is_lifetime() {
+                let lt = self.expect_lifetime();
+                Ok(self.recover_unclosed_char(lt.ident, err))
+            } else {
+                Err(err(self))
+            }
         })
     }
 
diff --git a/compiler/rustc_parse/src/parser/pat.rs b/compiler/rustc_parse/src/parser/pat.rs
index 56efec422d6..52c11b4e35f 100644
--- a/compiler/rustc_parse/src/parser/pat.rs
+++ b/compiler/rustc_parse/src/parser/pat.rs
@@ -402,6 +402,25 @@ impl<'a> Parser<'a> {
             } else {
                 PatKind::Path(qself, path)
             }
+        } else if matches!(self.token.kind, token::Lifetime(_))
+            // In pattern position, we're totally fine with using "next token isn't colon"
+            // as a heuristic. We could probably just always try to recover if it's a lifetime,
+            // because we never have `'a: label {}` in a pattern position anyways, but it does
+            // keep us from suggesting something like `let 'a: Ty = ..` => `let 'a': Ty = ..`
+            && !self.look_ahead(1, |token| matches!(token.kind, token::Colon))
+        {
+            // Recover a `'a` as a `'a'` literal
+            let lt = self.expect_lifetime();
+            let lit = self.recover_unclosed_char(lt.ident, |self_| {
+                let expected = expected.unwrap_or("pattern");
+                let msg =
+                    format!("expected {}, found {}", expected, super::token_descr(&self_.token));
+
+                let mut err = self_.struct_span_err(self_.token.span, &msg);
+                err.span_label(self_.token.span, format!("expected {}", expected));
+                err
+            });
+            PatKind::Lit(self.mk_expr(lo, ExprKind::Lit(lit)))
         } else {
             // Try to parse everything else as literal with optional minus
             match self.parse_literal_maybe_minus() {
@@ -799,6 +818,7 @@ impl<'a> Parser<'a> {
                 || t.kind == token::Dot // e.g. `.5` for recovery;
                 || t.can_begin_literal_maybe_minus() // e.g. `42`.
                 || t.is_whole_expr()
+                || t.is_lifetime() // recover `'a` instead of `'a'`
             })
     }
 
diff --git a/compiler/rustc_resolve/src/access_levels.rs b/compiler/rustc_resolve/src/access_levels.rs
index 1cef60949d8..257784341e3 100644
--- a/compiler/rustc_resolve/src/access_levels.rs
+++ b/compiler/rustc_resolve/src/access_levels.rs
@@ -1,5 +1,4 @@
-use crate::NameBindingKind;
-use crate::Resolver;
+use crate::{ImportKind, NameBindingKind, Resolver};
 use rustc_ast::ast;
 use rustc_ast::visit;
 use rustc_ast::visit::Visitor;
@@ -45,14 +44,13 @@ impl<'r, 'a> AccessLevelsVisitor<'r, 'a> {
         let module = self.r.get_module(module_id.to_def_id()).unwrap();
         let resolutions = self.r.resolutions(module);
 
-        for (key, name_resolution) in resolutions.borrow().iter() {
+        for (_, name_resolution) in resolutions.borrow().iter() {
             if let Some(mut binding) = name_resolution.borrow().binding() && !binding.is_ambiguity() {
                 // Set the given binding access level to `AccessLevel::Public` and
                 // sets the rest of the `use` chain to `AccessLevel::Exported` until
                 // we hit the actual exported item.
 
-                // FIXME: tag and is_public() condition must be deleted,
-                // but assertion fail occurs in import_id_for_ns
+                // FIXME: tag and is_public() condition should be removed, but assertions occur.
                 let tag = if binding.is_import() { AccessLevel::Exported } else { AccessLevel::Public };
                 if binding.vis.is_public() {
                     let mut prev_parent_id = module_id;
@@ -60,16 +58,26 @@ impl<'r, 'a> AccessLevelsVisitor<'r, 'a> {
                     while let NameBindingKind::Import { binding: nested_binding, import, .. } =
                         binding.kind
                     {
-                        let id = self.r.local_def_id(self.r.import_id_for_ns(import, key.ns));
-                        self.update(
-                            id,
+                        let mut update = |node_id| self.update(
+                            self.r.local_def_id(node_id),
                             binding.vis.expect_local(),
                             prev_parent_id,
                             level,
                         );
+                        // In theory all the import IDs have individual visibilities and effective
+                        // visibilities, but in practice these IDs go straigth to HIR where all
+                        // their few uses assume that their (effective) visibility applies to the
+                        // whole syntactic `use` item. So we update them all to the maximum value
+                        // among the potential individual effective visibilities. Maybe HIR for
+                        // imports shouldn't use three IDs at all.
+                        update(import.id);
+                        if let ImportKind::Single { additional_ids, .. } = import.kind {
+                            update(additional_ids.0);
+                            update(additional_ids.1);
+                        }
 
                         level = AccessLevel::Exported;
-                        prev_parent_id = id;
+                        prev_parent_id = self.r.local_def_id(import.id);
                         binding = nested_binding;
                     }
                 }
diff --git a/compiler/rustc_resolve/src/imports.rs b/compiler/rustc_resolve/src/imports.rs
index 0a86374d76d..f2cc50c199f 100644
--- a/compiler/rustc_resolve/src/imports.rs
+++ b/compiler/rustc_resolve/src/imports.rs
@@ -2,7 +2,7 @@
 
 use crate::diagnostics::{import_candidates, Suggestion};
 use crate::Determinacy::{self, *};
-use crate::Namespace::{self, *};
+use crate::Namespace::*;
 use crate::{module_to_string, names_to_string, ImportSuggestion};
 use crate::{AmbiguityKind, BindingKey, ModuleKind, ResolutionError, Resolver, Segment};
 use crate::{Finalize, Module, ModuleOrUniformRoot, ParentScope, PerNS, ScopeSet};
@@ -371,31 +371,6 @@ impl<'a> Resolver<'a> {
             self.used_imports.insert(import.id);
         }
     }
-
-    /// Take primary and additional node IDs from an import and select one that corresponds to the
-    /// given namespace. The logic must match the corresponding logic from `fn lower_use_tree` that
-    /// assigns resolutons to IDs.
-    pub(crate) fn import_id_for_ns(&self, import: &Import<'_>, ns: Namespace) -> NodeId {
-        if let ImportKind::Single { additional_ids: (id1, id2), .. } = import.kind {
-            if let Some(resolutions) = self.import_res_map.get(&import.id) {
-                assert!(resolutions[ns].is_some(), "incorrectly finalized import");
-                return match ns {
-                    TypeNS => import.id,
-                    ValueNS => match resolutions.type_ns {
-                        Some(_) => id1,
-                        None => import.id,
-                    },
-                    MacroNS => match (resolutions.type_ns, resolutions.value_ns) {
-                        (Some(_), Some(_)) => id2,
-                        (Some(_), None) | (None, Some(_)) => id1,
-                        (None, None) => import.id,
-                    },
-                };
-            }
-        }
-
-        import.id
-    }
 }
 
 /// An error that may be transformed into a diagnostic later. Used to combine multiple unresolved
diff --git a/compiler/rustc_resolve/src/late.rs b/compiler/rustc_resolve/src/late.rs
index ba3d8f64bbc..58853346a92 100644
--- a/compiler/rustc_resolve/src/late.rs
+++ b/compiler/rustc_resolve/src/late.rs
@@ -524,6 +524,9 @@ struct DiagnosticMetadata<'ast> {
     /// Used to detect possible `if let` written without `let` and to provide structured suggestion.
     in_if_condition: Option<&'ast Expr>,
 
+    /// Used to detect possible new binding written without `let` and to provide structured suggestion.
+    in_assignment: Option<&'ast Expr>,
+
     /// If we are currently in a trait object definition. Used to point at the bounds when
     /// encountering a struct or enum.
     current_trait_object: Option<&'ast [ast::GenericBound]>,
@@ -3905,6 +3908,11 @@ impl<'a: 'ast, 'b, 'ast> LateResolutionVisitor<'a, 'b, 'ast> {
                 self.resolve_expr(elem, Some(expr));
                 self.visit_expr(idx);
             }
+            ExprKind::Assign(..) => {
+                let old = self.diagnostic_metadata.in_assignment.replace(expr);
+                visit::walk_expr(self, expr);
+                self.diagnostic_metadata.in_assignment = old;
+            }
             _ => {
                 visit::walk_expr(self, expr);
             }
diff --git a/compiler/rustc_resolve/src/late/diagnostics.rs b/compiler/rustc_resolve/src/late/diagnostics.rs
index e3dba2366a4..23c5fac8b71 100644
--- a/compiler/rustc_resolve/src/late/diagnostics.rs
+++ b/compiler/rustc_resolve/src/late/diagnostics.rs
@@ -708,7 +708,9 @@ impl<'a: 'ast, 'ast> LateResolutionVisitor<'a, '_, 'ast> {
 
             // If the trait has a single item (which wasn't matched by Levenshtein), suggest it
             let suggestion = self.get_single_associated_item(&path, &source, is_expected);
-            self.r.add_typo_suggestion(err, suggestion, ident_span);
+            if !self.r.add_typo_suggestion(err, suggestion, ident_span) {
+                fallback = !self.let_binding_suggestion(err, ident_span);
+            }
         }
         fallback
     }
@@ -1105,41 +1107,14 @@ impl<'a: 'ast, 'ast> LateResolutionVisitor<'a, '_, 'ast> {
         // where a brace being opened means a block is being started. Look
         // ahead for the next text to see if `span` is followed by a `{`.
         let sm = self.r.session.source_map();
-        let mut sp = span;
-        loop {
-            sp = sm.next_point(sp);
-            match sm.span_to_snippet(sp) {
-                Ok(ref snippet) => {
-                    if snippet.chars().any(|c| !c.is_whitespace()) {
-                        break;
-                    }
-                }
-                _ => break,
-            }
-        }
+        let sp = sm.span_look_ahead(span, None, Some(50));
         let followed_by_brace = matches!(sm.span_to_snippet(sp), Ok(ref snippet) if snippet == "{");
         // In case this could be a struct literal that needs to be surrounded
         // by parentheses, find the appropriate span.
-        let mut i = 0;
-        let mut closing_brace = None;
-        loop {
-            sp = sm.next_point(sp);
-            match sm.span_to_snippet(sp) {
-                Ok(ref snippet) => {
-                    if snippet == "}" {
-                        closing_brace = Some(span.to(sp));
-                        break;
-                    }
-                }
-                _ => break,
-            }
-            i += 1;
-            // The bigger the span, the more likely we're incorrect --
-            // bound it to 100 chars long.
-            if i > 100 {
-                break;
-            }
-        }
+        let closing_span = sm.span_look_ahead(span, Some("}"), Some(50));
+        let closing_brace: Option<Span> = sm
+            .span_to_snippet(closing_span)
+            .map_or(None, |s| if s == "}" { Some(span.to(closing_span)) } else { None });
         (followed_by_brace, closing_brace)
     }
 
@@ -1779,26 +1754,16 @@ impl<'a: 'ast, 'ast> LateResolutionVisitor<'a, '_, 'ast> {
                             }
                         }
                         if let Ok(base_snippet) = base_snippet {
-                            let mut sp = after_colon_sp;
-                            for _ in 0..100 {
-                                // Try to find an assignment
-                                sp = sm.next_point(sp);
-                                let snippet = sm.span_to_snippet(sp);
-                                match snippet {
-                                    Ok(ref x) if x.as_str() == "=" => {
-                                        err.span_suggestion(
-                                            base_span,
-                                            "maybe you meant to write an assignment here",
-                                            format!("let {}", base_snippet),
-                                            Applicability::MaybeIncorrect,
-                                        );
-                                        show_label = false;
-                                        break;
-                                    }
-                                    Ok(ref x) if x.as_str() == "\n" => break,
-                                    Err(_) => break,
-                                    Ok(_) => {}
-                                }
+                            // Try to find an assignment
+                            let eq_span = sm.span_look_ahead(after_colon_sp, Some("="), Some(50));
+                            if let Ok(ref snippet) = sm.span_to_snippet(eq_span) && snippet == "=" {
+                                err.span_suggestion(
+                                    base_span,
+                                    "maybe you meant to write an assignment here",
+                                    format!("let {}", base_snippet),
+                                    Applicability::MaybeIncorrect,
+                                );
+                                show_label = false;
                             }
                         }
                     }
@@ -1815,6 +1780,31 @@ impl<'a: 'ast, 'ast> LateResolutionVisitor<'a, '_, 'ast> {
         false
     }
 
+    fn let_binding_suggestion(&self, err: &mut Diagnostic, ident_span: Span) -> bool {
+        // try to give a suggestion for this pattern: `name = 1`, which is common in other languages
+        let mut added_suggestion = false;
+        if let Some(Expr { kind: ExprKind::Assign(lhs, _rhs, _), .. }) = self.diagnostic_metadata.in_assignment &&
+            let ast::ExprKind::Path(None, _) = lhs.kind {
+                let sm = self.r.session.source_map();
+                let line_span = sm.span_extend_to_line(ident_span);
+                let ident_name = sm.span_to_snippet(ident_span).unwrap();
+                // HACK(chenyukang): make sure ident_name is at the starting of the line to protect against macros
+                if sm
+                    .span_to_snippet(line_span)
+                    .map_or(false, |s| s.trim().starts_with(&ident_name))
+                {
+                    err.span_suggestion_verbose(
+                        ident_span.shrink_to_lo(),
+                        "you might have meant to introduce a new binding",
+                        "let ".to_string(),
+                        Applicability::MaybeIncorrect,
+                    );
+                    added_suggestion = true;
+                }
+            }
+        added_suggestion
+    }
+
     fn find_module(&mut self, def_id: DefId) -> Option<(Module<'a>, ImportSuggestion)> {
         let mut result = None;
         let mut seen_modules = FxHashSet::default();
diff --git a/compiler/rustc_span/src/source_map.rs b/compiler/rustc_span/src/source_map.rs
index 506ce6955d3..f9566eeee94 100644
--- a/compiler/rustc_span/src/source_map.rs
+++ b/compiler/rustc_span/src/source_map.rs
@@ -877,6 +877,26 @@ impl SourceMap {
         Span::new(BytePos(start_of_next_point), end_of_next_point, sp.ctxt(), None)
     }
 
+    /// Returns a new span to check next none-whitespace character or some specified expected character
+    /// If `expect` is none, the first span of non-whitespace character is returned.
+    /// If `expect` presented, the first span of the character `expect` is returned
+    /// Otherwise, the span reached to limit is returned.
+    pub fn span_look_ahead(&self, span: Span, expect: Option<&str>, limit: Option<usize>) -> Span {
+        let mut sp = span;
+        for _ in 0..limit.unwrap_or(100 as usize) {
+            sp = self.next_point(sp);
+            if let Ok(ref snippet) = self.span_to_snippet(sp) {
+                if expect.map_or(false, |es| snippet == es) {
+                    break;
+                }
+                if expect.is_none() && snippet.chars().any(|c| !c.is_whitespace()) {
+                    break;
+                }
+            }
+        }
+        sp
+    }
+
     /// Finds the width of the character, either before or after the end of provided span,
     /// depending on the `forwards` parameter.
     fn find_width_of_character_at_span(&self, sp: Span, forwards: bool) -> u32 {
diff --git a/src/librustdoc/clean/blanket_impl.rs b/src/librustdoc/clean/blanket_impl.rs
index 95061ae61e3..7c59e785752 100644
--- a/src/librustdoc/clean/blanket_impl.rs
+++ b/src/librustdoc/clean/blanket_impl.rs
@@ -13,116 +13,124 @@ pub(crate) struct BlanketImplFinder<'a, 'tcx> {
 
 impl<'a, 'tcx> BlanketImplFinder<'a, 'tcx> {
     pub(crate) fn get_blanket_impls(&mut self, item_def_id: DefId) -> Vec<Item> {
-        let param_env = self.cx.tcx.param_env(item_def_id);
-        let ty = self.cx.tcx.bound_type_of(item_def_id);
+        let cx = &mut self.cx;
+        let param_env = cx.tcx.param_env(item_def_id);
+        let ty = cx.tcx.bound_type_of(item_def_id);
 
         trace!("get_blanket_impls({:?})", ty);
         let mut impls = Vec::new();
-        self.cx.with_all_traits(|cx, all_traits| {
-            for &trait_def_id in all_traits {
-                if !cx.cache.access_levels.is_public(trait_def_id)
-                    || cx.generated_synthetics.get(&(ty.0, trait_def_id)).is_some()
-                {
+        for trait_def_id in cx.tcx.all_traits() {
+            if !cx.cache.access_levels.is_public(trait_def_id)
+                || cx.generated_synthetics.get(&(ty.0, trait_def_id)).is_some()
+            {
+                continue;
+            }
+            // NOTE: doesn't use `for_each_relevant_impl` to avoid looking at anything besides blanket impls
+            let trait_impls = cx.tcx.trait_impls_of(trait_def_id);
+            'blanket_impls: for &impl_def_id in trait_impls.blanket_impls() {
+                trace!(
+                    "get_blanket_impls: Considering impl for trait '{:?}' {:?}",
+                    trait_def_id,
+                    impl_def_id
+                );
+                let trait_ref = cx.tcx.bound_impl_trait_ref(impl_def_id).unwrap();
+                if !matches!(trait_ref.0.self_ty().kind(), ty::Param(_)) {
                     continue;
                 }
-                // NOTE: doesn't use `for_each_relevant_impl` to avoid looking at anything besides blanket impls
-                let trait_impls = cx.tcx.trait_impls_of(trait_def_id);
-                'blanket_impls: for &impl_def_id in trait_impls.blanket_impls() {
-                    trace!(
-                        "get_blanket_impls: Considering impl for trait '{:?}' {:?}",
-                        trait_def_id,
-                        impl_def_id
-                    );
-                    let trait_ref = cx.tcx.bound_impl_trait_ref(impl_def_id).unwrap();
-                    if !matches!(trait_ref.0.self_ty().kind(), ty::Param(_)) {
-                        continue;
-                    }
-                    let infcx = cx.tcx.infer_ctxt().build();
-                    let substs = infcx.fresh_substs_for_item(DUMMY_SP, item_def_id);
-                    let impl_ty = ty.subst(infcx.tcx, substs);
-                    let param_env = EarlyBinder(param_env).subst(infcx.tcx, substs);
+                let infcx = cx.tcx.infer_ctxt().build();
+                let substs = infcx.fresh_substs_for_item(DUMMY_SP, item_def_id);
+                let impl_ty = ty.subst(infcx.tcx, substs);
+                let param_env = EarlyBinder(param_env).subst(infcx.tcx, substs);
 
-                    let impl_substs = infcx.fresh_substs_for_item(DUMMY_SP, impl_def_id);
-                    let impl_trait_ref = trait_ref.subst(infcx.tcx, impl_substs);
+                let impl_substs = infcx.fresh_substs_for_item(DUMMY_SP, impl_def_id);
+                let impl_trait_ref = trait_ref.subst(infcx.tcx, impl_substs);
 
-                    // Require the type the impl is implemented on to match
-                    // our type, and ignore the impl if there was a mismatch.
-                    let cause = traits::ObligationCause::dummy();
-                    let Ok(eq_result) = infcx.at(&cause, param_env).eq(impl_trait_ref.self_ty(), impl_ty) else {
+                // Require the type the impl is implemented on to match
+                // our type, and ignore the impl if there was a mismatch.
+                let cause = traits::ObligationCause::dummy();
+                let Ok(eq_result) = infcx.at(&cause, param_env).eq(impl_trait_ref.self_ty(), impl_ty) else {
                         continue
                     };
-                    let InferOk { value: (), obligations } = eq_result;
-                    // FIXME(eddyb) ignoring `obligations` might cause false positives.
-                    drop(obligations);
+                let InferOk { value: (), obligations } = eq_result;
+                // FIXME(eddyb) ignoring `obligations` might cause false positives.
+                drop(obligations);
 
-                    trace!(
-                        "invoking predicate_may_hold: param_env={:?}, impl_trait_ref={:?}, impl_ty={:?}",
+                trace!(
+                    "invoking predicate_may_hold: param_env={:?}, impl_trait_ref={:?}, impl_ty={:?}",
+                    param_env,
+                    impl_trait_ref,
+                    impl_ty
+                );
+                let predicates = cx
+                    .tcx
+                    .predicates_of(impl_def_id)
+                    .instantiate(cx.tcx, impl_substs)
+                    .predicates
+                    .into_iter()
+                    .chain(Some(
+                        ty::Binder::dummy(impl_trait_ref)
+                            .to_poly_trait_predicate()
+                            .map_bound(ty::PredicateKind::Trait)
+                            .to_predicate(infcx.tcx),
+                    ));
+                for predicate in predicates {
+                    debug!("testing predicate {:?}", predicate);
+                    let obligation = traits::Obligation::new(
+                        traits::ObligationCause::dummy(),
                         param_env,
-                        impl_trait_ref,
-                        impl_ty
+                        predicate,
                     );
-                    let predicates = cx
-                        .tcx
-                        .predicates_of(impl_def_id)
-                        .instantiate(cx.tcx, impl_substs)
-                        .predicates
-                        .into_iter()
-                        .chain(Some(
-                            ty::Binder::dummy(impl_trait_ref)
-                                .to_poly_trait_predicate()
-                                .map_bound(ty::PredicateKind::Trait)
-                                .to_predicate(infcx.tcx),
-                        ));
-                    for predicate in predicates {
-                        debug!("testing predicate {:?}", predicate);
-                        let obligation = traits::Obligation::new(
-                            traits::ObligationCause::dummy(),
-                            param_env,
-                            predicate,
-                        );
-                        match infcx.evaluate_obligation(&obligation) {
-                            Ok(eval_result) if eval_result.may_apply() => {}
-                            Err(traits::OverflowError::Canonical) => {}
-                            Err(traits::OverflowError::ErrorReporting) => {}
-                            _ => continue 'blanket_impls,
-                        }
+                    match infcx.evaluate_obligation(&obligation) {
+                        Ok(eval_result) if eval_result.may_apply() => {}
+                        Err(traits::OverflowError::Canonical) => {}
+                        Err(traits::OverflowError::ErrorReporting) => {}
+                        _ => continue 'blanket_impls,
                     }
-                    debug!(
-                        "get_blanket_impls: found applicable impl for trait_ref={:?}, ty={:?}",
-                        trait_ref, ty
-                    );
+                }
+                debug!(
+                    "get_blanket_impls: found applicable impl for trait_ref={:?}, ty={:?}",
+                    trait_ref, ty
+                );
 
-                    cx.generated_synthetics.insert((ty.0, trait_def_id));
+                cx.generated_synthetics.insert((ty.0, trait_def_id));
 
-                    impls.push(Item {
-                        name: None,
-                        attrs: Default::default(),
-                        visibility: Inherited,
-                        item_id: ItemId::Blanket { impl_id: impl_def_id, for_: item_def_id },
-                        kind: Box::new(ImplItem(Box::new(Impl {
-                            unsafety: hir::Unsafety::Normal,
-                            generics: clean_ty_generics(
-                                cx,
-                                cx.tcx.generics_of(impl_def_id),
-                                cx.tcx.explicit_predicates_of(impl_def_id),
-                            ),
-                            // FIXME(eddyb) compute both `trait_` and `for_` from
-                            // the post-inference `trait_ref`, as it's more accurate.
-                            trait_: Some(clean_trait_ref_with_bindings(cx, trait_ref.0, ThinVec::new())),
-                            for_: clean_middle_ty(ty.0, cx, None),
-                            items: cx.tcx
-                                .associated_items(impl_def_id)
-                                .in_definition_order()
-                                .map(|x| clean_middle_assoc_item(x, cx))
-                                .collect::<Vec<_>>(),
-                            polarity: ty::ImplPolarity::Positive,
-                            kind: ImplKind::Blanket(Box::new(clean_middle_ty(trait_ref.0.self_ty(), cx, None))),
-                        }))),
-                        cfg: None,
-                    });
-                }
+                impls.push(Item {
+                    name: None,
+                    attrs: Default::default(),
+                    visibility: Inherited,
+                    item_id: ItemId::Blanket { impl_id: impl_def_id, for_: item_def_id },
+                    kind: Box::new(ImplItem(Box::new(Impl {
+                        unsafety: hir::Unsafety::Normal,
+                        generics: clean_ty_generics(
+                            cx,
+                            cx.tcx.generics_of(impl_def_id),
+                            cx.tcx.explicit_predicates_of(impl_def_id),
+                        ),
+                        // FIXME(eddyb) compute both `trait_` and `for_` from
+                        // the post-inference `trait_ref`, as it's more accurate.
+                        trait_: Some(clean_trait_ref_with_bindings(
+                            cx,
+                            trait_ref.0,
+                            ThinVec::new(),
+                        )),
+                        for_: clean_middle_ty(ty.0, cx, None),
+                        items: cx
+                            .tcx
+                            .associated_items(impl_def_id)
+                            .in_definition_order()
+                            .map(|x| clean_middle_assoc_item(x, cx))
+                            .collect::<Vec<_>>(),
+                        polarity: ty::ImplPolarity::Positive,
+                        kind: ImplKind::Blanket(Box::new(clean_middle_ty(
+                            trait_ref.0.self_ty(),
+                            cx,
+                            None,
+                        ))),
+                    }))),
+                    cfg: None,
+                });
             }
-        });
+        }
 
         impls
     }
diff --git a/src/librustdoc/core.rs b/src/librustdoc/core.rs
index 858e939bd96..8232353f915 100644
--- a/src/librustdoc/core.rs
+++ b/src/librustdoc/core.rs
@@ -38,7 +38,6 @@ pub(crate) struct ResolverCaches {
     /// Traits in scope for a given module.
     /// See `collect_intra_doc_links::traits_implemented_by` for more details.
     pub(crate) traits_in_scope: DefIdMap<Vec<TraitCandidate>>,
-    pub(crate) all_traits: Option<Vec<DefId>>,
     pub(crate) all_trait_impls: Option<Vec<DefId>>,
     pub(crate) all_macro_rules: FxHashMap<Symbol, Res<NodeId>>,
 }
@@ -134,12 +133,6 @@ impl<'tcx> DocContext<'tcx> {
         }
     }
 
-    pub(crate) fn with_all_traits(&mut self, f: impl FnOnce(&mut Self, &[DefId])) {
-        let all_traits = self.resolver_caches.all_traits.take();
-        f(self, all_traits.as_ref().expect("`all_traits` are already borrowed"));
-        self.resolver_caches.all_traits = all_traits;
-    }
-
     pub(crate) fn with_all_trait_impls(&mut self, f: impl FnOnce(&mut Self, &[DefId])) {
         let all_trait_impls = self.resolver_caches.all_trait_impls.take();
         f(self, all_trait_impls.as_ref().expect("`all_trait_impls` are already borrowed"));
@@ -353,14 +346,8 @@ pub(crate) fn run_global_ctxt(
     });
     rustc_passes::stability::check_unused_or_stable_features(tcx);
 
-    let auto_traits = resolver_caches
-        .all_traits
-        .as_ref()
-        .expect("`all_traits` are already borrowed")
-        .iter()
-        .copied()
-        .filter(|&trait_def_id| tcx.trait_is_auto(trait_def_id))
-        .collect();
+    let auto_traits =
+        tcx.all_traits().filter(|&trait_def_id| tcx.trait_is_auto(trait_def_id)).collect();
     let access_levels = tcx.privacy_access_levels(()).map_id(Into::into);
 
     let mut ctxt = DocContext {
diff --git a/src/librustdoc/passes/collect_intra_doc_links/early.rs b/src/librustdoc/passes/collect_intra_doc_links/early.rs
index 50dc26d768c..d121a3e2aa4 100644
--- a/src/librustdoc/passes/collect_intra_doc_links/early.rs
+++ b/src/librustdoc/passes/collect_intra_doc_links/early.rs
@@ -37,7 +37,6 @@ pub(crate) fn early_resolve_intra_doc_links(
         markdown_links: Default::default(),
         doc_link_resolutions: Default::default(),
         traits_in_scope: Default::default(),
-        all_traits: Default::default(),
         all_trait_impls: Default::default(),
         all_macro_rules: Default::default(),
         document_private_items,
@@ -63,7 +62,6 @@ pub(crate) fn early_resolve_intra_doc_links(
         markdown_links: Some(link_resolver.markdown_links),
         doc_link_resolutions: link_resolver.doc_link_resolutions,
         traits_in_scope: link_resolver.traits_in_scope,
-        all_traits: Some(link_resolver.all_traits),
         all_trait_impls: Some(link_resolver.all_trait_impls),
         all_macro_rules: link_resolver.all_macro_rules,
     }
@@ -81,7 +79,6 @@ struct EarlyDocLinkResolver<'r, 'ra> {
     markdown_links: FxHashMap<String, Vec<PreprocessedMarkdownLink>>,
     doc_link_resolutions: FxHashMap<(Symbol, Namespace, DefId), Option<Res<ast::NodeId>>>,
     traits_in_scope: DefIdMap<Vec<TraitCandidate>>,
-    all_traits: Vec<DefId>,
     all_trait_impls: Vec<DefId>,
     all_macro_rules: FxHashMap<Symbol, Res<ast::NodeId>>,
     document_private_items: bool,
@@ -122,8 +119,6 @@ impl<'ra> EarlyDocLinkResolver<'_, 'ra> {
         loop {
             let crates = Vec::from_iter(self.resolver.cstore().crates_untracked());
             for &cnum in &crates[start_cnum..] {
-                let all_traits =
-                    Vec::from_iter(self.resolver.cstore().traits_in_crate_untracked(cnum));
                 let all_trait_impls =
                     Vec::from_iter(self.resolver.cstore().trait_impls_in_crate_untracked(cnum));
                 let all_inherent_impls =
@@ -132,20 +127,18 @@ impl<'ra> EarlyDocLinkResolver<'_, 'ra> {
                     self.resolver.cstore().incoherent_impls_in_crate_untracked(cnum),
                 );
 
-                // Querying traits in scope is expensive so we try to prune the impl and traits lists
-                // using privacy, private traits and impls from other crates are never documented in
+                // Querying traits in scope is expensive so we try to prune the impl lists using
+                // privacy, private traits and impls from other crates are never documented in
                 // the current crate, and links in their doc comments are not resolved.
-                for &def_id in &all_traits {
-                    if self.resolver.cstore().visibility_untracked(def_id).is_public() {
-                        self.resolve_doc_links_extern_impl(def_id, false);
-                    }
-                }
                 for &(trait_def_id, impl_def_id, simplified_self_ty) in &all_trait_impls {
                     if self.resolver.cstore().visibility_untracked(trait_def_id).is_public()
                         && simplified_self_ty.and_then(|ty| ty.def()).map_or(true, |ty_def_id| {
                             self.resolver.cstore().visibility_untracked(ty_def_id).is_public()
                         })
                     {
+                        if self.visited_mods.insert(trait_def_id) {
+                            self.resolve_doc_links_extern_impl(trait_def_id, false);
+                        }
                         self.resolve_doc_links_extern_impl(impl_def_id, false);
                     }
                 }
@@ -158,7 +151,6 @@ impl<'ra> EarlyDocLinkResolver<'_, 'ra> {
                     self.resolve_doc_links_extern_impl(impl_def_id, true);
                 }
 
-                self.all_traits.extend(all_traits);
                 self.all_trait_impls
                     .extend(all_trait_impls.into_iter().map(|(_, def_id, _)| def_id));
             }
@@ -307,15 +299,20 @@ impl<'ra> EarlyDocLinkResolver<'_, 'ra> {
             {
                 if let Some(def_id) = child.res.opt_def_id() && !def_id.is_local() {
                     let scope_id = match child.res {
-                        Res::Def(DefKind::Variant, ..) => self.resolver.parent(def_id),
+                        Res::Def(
+                            DefKind::Variant
+                            | DefKind::AssocTy
+                            | DefKind::AssocFn
+                            | DefKind::AssocConst,
+                            ..,
+                        ) => self.resolver.parent(def_id),
                         _ => def_id,
                     };
                     self.resolve_doc_links_extern_outer(def_id, scope_id); // Outer attribute scope
                     if let Res::Def(DefKind::Mod, ..) = child.res {
                         self.resolve_doc_links_extern_inner(def_id); // Inner attribute scope
                     }
-                    // `DefKind::Trait`s are processed in `process_extern_impls`.
-                    if let Res::Def(DefKind::Mod | DefKind::Enum, ..) = child.res {
+                    if let Res::Def(DefKind::Mod | DefKind::Enum | DefKind::Trait, ..) = child.res {
                         self.process_module_children_or_reexports(def_id);
                     }
                     if let Res::Def(DefKind::Struct | DefKind::Union | DefKind::Variant, _) =
@@ -357,9 +354,6 @@ impl Visitor<'_> for EarlyDocLinkResolver<'_, '_> {
             self.parent_scope.module = old_module;
         } else {
             match &item.kind {
-                ItemKind::Trait(..) => {
-                    self.all_traits.push(self.resolver.local_def_id(item.id).to_def_id());
-                }
                 ItemKind::Impl(box ast::Impl { of_trait: Some(..), .. }) => {
                     self.all_trait_impls.push(self.resolver.local_def_id(item.id).to_def_id());
                 }
diff --git a/src/test/ui/issues/issue-23338-params-outlive-temps-of-body.rs b/src/test/ui/borrowck/issue-23338-params-outlive-temps-of-body.rs
index d45aaa843fb..d45aaa843fb 100644
--- a/src/test/ui/issues/issue-23338-params-outlive-temps-of-body.rs
+++ b/src/test/ui/borrowck/issue-23338-params-outlive-temps-of-body.rs
diff --git a/src/test/ui/issues/issue-23338-ensure-param-drop-order.rs b/src/test/ui/drop/issue-23338-ensure-param-drop-order.rs
index a99f260dde3..a99f260dde3 100644
--- a/src/test/ui/issues/issue-23338-ensure-param-drop-order.rs
+++ b/src/test/ui/drop/issue-23338-ensure-param-drop-order.rs
diff --git a/src/test/ui/issues/issue-24805-dropck-itemless.rs b/src/test/ui/dropck/issue-24805-dropck-itemless.rs
index 45761b61c3e..45761b61c3e 100644
--- a/src/test/ui/issues/issue-24805-dropck-itemless.rs
+++ b/src/test/ui/dropck/issue-24805-dropck-itemless.rs
diff --git a/src/test/ui/issues/issue-36053.rs b/src/test/ui/inference/issue-36053.rs
index 5c6d0780416..5c6d0780416 100644
--- a/src/test/ui/issues/issue-36053.rs
+++ b/src/test/ui/inference/issue-36053.rs
diff --git a/src/test/ui/parser/issues/issue-93282.rs b/src/test/ui/parser/issues/issue-93282.rs
index 261fcb5f918..274245f1a46 100644
--- a/src/test/ui/parser/issues/issue-93282.rs
+++ b/src/test/ui/parser/issues/issue-93282.rs
@@ -12,4 +12,5 @@ fn foo() {
     let x = 1;
     bar('y, x);
     //~^ ERROR expected
+    //~| ERROR mismatched types
 }
diff --git a/src/test/ui/parser/issues/issue-93282.stderr b/src/test/ui/parser/issues/issue-93282.stderr
index ee554784b3a..c6140bb821e 100644
--- a/src/test/ui/parser/issues/issue-93282.stderr
+++ b/src/test/ui/parser/issues/issue-93282.stderr
@@ -3,6 +3,11 @@ error: expected `while`, `for`, `loop` or `{` after a label
    |
 LL |     f<'a,>
    |         ^ expected `while`, `for`, `loop` or `{` after a label
+   |
+help: add `'` to close the char literal
+   |
+LL |     f<'a',>
+   |         +
 
 error: expected one of `.`, `:`, `;`, `?`, `for`, `loop`, `while`, `}`, or an operator, found `,`
   --> $DIR/issue-93282.rs:2:9
@@ -20,6 +25,26 @@ error: expected `while`, `for`, `loop` or `{` after a label
    |
 LL |     bar('y, x);
    |           ^ expected `while`, `for`, `loop` or `{` after a label
+   |
+help: add `'` to close the char literal
+   |
+LL |     bar('y', x);
+   |           +
+
+error[E0308]: mismatched types
+  --> $DIR/issue-93282.rs:13:9
+   |
+LL |     bar('y, x);
+   |     --- ^^ expected `usize`, found `char`
+   |     |
+   |     arguments to this function are incorrect
+   |
+note: function defined here
+  --> $DIR/issue-93282.rs:7:4
+   |
+LL | fn bar(a: usize, b: usize) -> usize {
+   |    ^^^ --------
 
-error: aborting due to 3 previous errors
+error: aborting due to 4 previous errors
 
+For more information about this error, try `rustc --explain E0308`.
diff --git a/src/test/ui/parser/label-is-actually-char.rs b/src/test/ui/parser/label-is-actually-char.rs
new file mode 100644
index 00000000000..183da603da4
--- /dev/null
+++ b/src/test/ui/parser/label-is-actually-char.rs
@@ -0,0 +1,16 @@
+fn main() {
+    let c = 'a;
+    //~^ ERROR expected `while`, `for`, `loop` or `{` after a label
+    //~| HELP add `'` to close the char literal
+    match c {
+        'a'..='b => {}
+        //~^ ERROR unexpected token: `'b`
+        //~| HELP add `'` to close the char literal
+        _ => {}
+    }
+    let x = ['a, 'b];
+    //~^ ERROR expected `while`, `for`, `loop` or `{` after a label
+    //~| ERROR expected `while`, `for`, `loop` or `{` after a label
+    //~| HELP add `'` to close the char literal
+    //~| HELP add `'` to close the char literal
+}
diff --git a/src/test/ui/parser/label-is-actually-char.stderr b/src/test/ui/parser/label-is-actually-char.stderr
new file mode 100644
index 00000000000..28c8d2ada3a
--- /dev/null
+++ b/src/test/ui/parser/label-is-actually-char.stderr
@@ -0,0 +1,46 @@
+error: expected `while`, `for`, `loop` or `{` after a label
+  --> $DIR/label-is-actually-char.rs:2:15
+   |
+LL |     let c = 'a;
+   |               ^ expected `while`, `for`, `loop` or `{` after a label
+   |
+help: add `'` to close the char literal
+   |
+LL |     let c = 'a';
+   |               +
+
+error: unexpected token: `'b`
+  --> $DIR/label-is-actually-char.rs:6:15
+   |
+LL |         'a'..='b => {}
+   |               ^^
+   |
+help: add `'` to close the char literal
+   |
+LL |         'a'..='b' => {}
+   |                 +
+
+error: expected `while`, `for`, `loop` or `{` after a label
+  --> $DIR/label-is-actually-char.rs:11:16
+   |
+LL |     let x = ['a, 'b];
+   |                ^ expected `while`, `for`, `loop` or `{` after a label
+   |
+help: add `'` to close the char literal
+   |
+LL |     let x = ['a', 'b];
+   |                +
+
+error: expected `while`, `for`, `loop` or `{` after a label
+  --> $DIR/label-is-actually-char.rs:11:20
+   |
+LL |     let x = ['a, 'b];
+   |                    ^ expected `while`, `for`, `loop` or `{` after a label
+   |
+help: add `'` to close the char literal
+   |
+LL |     let x = ['a, 'b'];
+   |                    +
+
+error: aborting due to 4 previous errors
+
diff --git a/src/test/ui/parser/numeric-lifetime.stderr b/src/test/ui/parser/numeric-lifetime.stderr
index 73a828952b2..7c1bcb72631 100644
--- a/src/test/ui/parser/numeric-lifetime.stderr
+++ b/src/test/ui/parser/numeric-lifetime.stderr
@@ -1,3 +1,11 @@
+error[E0308]: mismatched types
+  --> $DIR/numeric-lifetime.rs:6:20
+   |
+LL |     let x: usize = "";
+   |            -----   ^^ expected `usize`, found `&str`
+   |            |
+   |            expected due to this
+
 error: lifetimes cannot start with a number
   --> $DIR/numeric-lifetime.rs:1:10
    |
@@ -10,14 +18,6 @@ error: lifetimes cannot start with a number
 LL | struct S<'1> { s: &'1 usize }
    |                    ^^
 
-error[E0308]: mismatched types
-  --> $DIR/numeric-lifetime.rs:6:20
-   |
-LL |     let x: usize = "";
-   |            -----   ^^ expected `usize`, found `&str`
-   |            |
-   |            expected due to this
-
 error: aborting due to 3 previous errors
 
 For more information about this error, try `rustc --explain E0308`.
diff --git a/src/test/ui/parser/require-parens-for-chained-comparison.rs b/src/test/ui/parser/require-parens-for-chained-comparison.rs
index f29fd7a5472..5b90e905a64 100644
--- a/src/test/ui/parser/require-parens-for-chained-comparison.rs
+++ b/src/test/ui/parser/require-parens-for-chained-comparison.rs
@@ -23,11 +23,13 @@ fn main() {
     //~^ ERROR expected one of
     //~| HELP use `::<...>` instead of `<...>` to specify lifetime, type, or const arguments
     //~| ERROR expected
+    //~| HELP add `'` to close the char literal
 
     f<'_>();
     //~^ comparison operators cannot be chained
     //~| HELP use `::<...>` instead of `<...>` to specify lifetime, type, or const arguments
     //~| ERROR expected
+    //~| HELP add `'` to close the char literal
 
     let _ = f<u8>;
     //~^ ERROR comparison operators cannot be chained
diff --git a/src/test/ui/parser/require-parens-for-chained-comparison.stderr b/src/test/ui/parser/require-parens-for-chained-comparison.stderr
index 0bf52854ec2..52e201c435c 100644
--- a/src/test/ui/parser/require-parens-for-chained-comparison.stderr
+++ b/src/test/ui/parser/require-parens-for-chained-comparison.stderr
@@ -58,6 +58,11 @@ error: expected `while`, `for`, `loop` or `{` after a label
    |
 LL |     let _ = f<'_, i8>();
    |                 ^ expected `while`, `for`, `loop` or `{` after a label
+   |
+help: add `'` to close the char literal
+   |
+LL |     let _ = f<'_', i8>();
+   |                 +
 
 error: expected one of `.`, `:`, `;`, `?`, `else`, `for`, `loop`, `while`, or an operator, found `,`
   --> $DIR/require-parens-for-chained-comparison.rs:22:17
@@ -71,13 +76,18 @@ LL |     let _ = f::<'_, i8>();
    |              ++
 
 error: expected `while`, `for`, `loop` or `{` after a label
-  --> $DIR/require-parens-for-chained-comparison.rs:27:9
+  --> $DIR/require-parens-for-chained-comparison.rs:28:9
    |
 LL |     f<'_>();
    |         ^ expected `while`, `for`, `loop` or `{` after a label
+   |
+help: add `'` to close the char literal
+   |
+LL |     f<'_'>();
+   |         +
 
 error: comparison operators cannot be chained
-  --> $DIR/require-parens-for-chained-comparison.rs:27:6
+  --> $DIR/require-parens-for-chained-comparison.rs:28:6
    |
 LL |     f<'_>();
    |      ^  ^
@@ -88,7 +98,7 @@ LL |     f::<'_>();
    |      ++
 
 error: comparison operators cannot be chained
-  --> $DIR/require-parens-for-chained-comparison.rs:32:14
+  --> $DIR/require-parens-for-chained-comparison.rs:34:14
    |
 LL |     let _ = f<u8>;
    |              ^  ^
diff --git a/src/test/ui/privacy/access_levels.rs b/src/test/ui/privacy/access_levels.rs
index cc074a4f958..42c9975bedb 100644
--- a/src/test/ui/privacy/access_levels.rs
+++ b/src/test/ui/privacy/access_levels.rs
@@ -70,5 +70,6 @@ mod half_public_import {
 
 #[rustc_effective_visibility]
 pub use half_public_import::HalfPublicImport; //~ ERROR Public: pub, Exported: pub, Reachable: pub, ReachableFromImplTrait: pub
+                                              //~^ ERROR Public: pub, Exported: pub, Reachable: pub, ReachableFromImplTrait: pub
 
 fn main() {}
diff --git a/src/test/ui/privacy/access_levels.stderr b/src/test/ui/privacy/access_levels.stderr
index 19199a3eb87..111e02bc329 100644
--- a/src/test/ui/privacy/access_levels.stderr
+++ b/src/test/ui/privacy/access_levels.stderr
@@ -112,6 +112,12 @@ error: Public: pub, Exported: pub, Reachable: pub, ReachableFromImplTrait: pub
 LL | pub use half_public_import::HalfPublicImport;
    |         ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
 
+error: Public: pub, Exported: pub, Reachable: pub, ReachableFromImplTrait: pub
+  --> $DIR/access_levels.rs:72:9
+   |
+LL | pub use half_public_import::HalfPublicImport;
+   |         ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+
 error: Public: pub(crate), Exported: pub, Reachable: pub, ReachableFromImplTrait: pub
   --> $DIR/access_levels.rs:14:13
    |
@@ -124,5 +130,5 @@ error: Public: pub(crate), Exported: pub, Reachable: pub, ReachableFromImplTrait
 LL |             type B;
    |             ^^^^^^
 
-error: aborting due to 21 previous errors
+error: aborting due to 22 previous errors
 
diff --git a/src/test/ui/issues/issue-64620.rs b/src/test/ui/return/issue-64620.rs
index a62e5bf8d3c..a62e5bf8d3c 100644
--- a/src/test/ui/issues/issue-64620.rs
+++ b/src/test/ui/return/issue-64620.rs
diff --git a/src/test/ui/issues/issue-64620.stderr b/src/test/ui/return/issue-64620.stderr
index f40ac4de32d..f40ac4de32d 100644
--- a/src/test/ui/issues/issue-64620.stderr
+++ b/src/test/ui/return/issue-64620.stderr
diff --git a/src/test/ui/issues/issue-2718-a.rs b/src/test/ui/structs-enums/issue-2718-a.rs
index 6c491584540..6c491584540 100644
--- a/src/test/ui/issues/issue-2718-a.rs
+++ b/src/test/ui/structs-enums/issue-2718-a.rs
diff --git a/src/test/ui/issues/issue-2718-a.stderr b/src/test/ui/structs-enums/issue-2718-a.stderr
index 7ea620f386a..7ea620f386a 100644
--- a/src/test/ui/issues/issue-2718-a.stderr
+++ b/src/test/ui/structs-enums/issue-2718-a.stderr
diff --git a/src/test/ui/suggestions/suggest-let-for-assignment.fixed b/src/test/ui/suggestions/suggest-let-for-assignment.fixed
new file mode 100644
index 00000000000..3a25e25eede
--- /dev/null
+++ b/src/test/ui/suggestions/suggest-let-for-assignment.fixed
@@ -0,0 +1,17 @@
+// run-rustfix
+
+fn main() {
+    let demo = 1; //~ ERROR cannot find value `demo` in this scope
+    dbg!(demo); //~ ERROR cannot find value `demo` in this scope
+
+    let x = "x"; //~ ERROR cannot find value `x` in this scope
+    println!("x: {}", x); //~ ERROR cannot find value `x` in this scope
+
+    if x == "x" {
+        //~^ ERROR cannot find value `x` in this scope
+        println!("x is 1");
+    }
+
+    let y = 1 + 2; //~ ERROR cannot find value `y` in this scope
+    println!("y: {}", y); //~ ERROR cannot find value `y` in this scope
+}
diff --git a/src/test/ui/suggestions/suggest-let-for-assignment.rs b/src/test/ui/suggestions/suggest-let-for-assignment.rs
new file mode 100644
index 00000000000..67705fe063a
--- /dev/null
+++ b/src/test/ui/suggestions/suggest-let-for-assignment.rs
@@ -0,0 +1,17 @@
+// run-rustfix
+
+fn main() {
+    demo = 1; //~ ERROR cannot find value `demo` in this scope
+    dbg!(demo); //~ ERROR cannot find value `demo` in this scope
+
+    x = "x"; //~ ERROR cannot find value `x` in this scope
+    println!("x: {}", x); //~ ERROR cannot find value `x` in this scope
+
+    if x == "x" {
+        //~^ ERROR cannot find value `x` in this scope
+        println!("x is 1");
+    }
+
+    y = 1 + 2; //~ ERROR cannot find value `y` in this scope
+    println!("y: {}", y); //~ ERROR cannot find value `y` in this scope
+}
diff --git a/src/test/ui/suggestions/suggest-let-for-assignment.stderr b/src/test/ui/suggestions/suggest-let-for-assignment.stderr
new file mode 100644
index 00000000000..3f6a3da4be2
--- /dev/null
+++ b/src/test/ui/suggestions/suggest-let-for-assignment.stderr
@@ -0,0 +1,60 @@
+error[E0425]: cannot find value `demo` in this scope
+  --> $DIR/suggest-let-for-assignment.rs:4:5
+   |
+LL |     demo = 1;
+   |     ^^^^
+   |
+help: you might have meant to introduce a new binding
+   |
+LL |     let demo = 1;
+   |     +++
+
+error[E0425]: cannot find value `demo` in this scope
+  --> $DIR/suggest-let-for-assignment.rs:5:10
+   |
+LL |     dbg!(demo);
+   |          ^^^^ not found in this scope
+
+error[E0425]: cannot find value `x` in this scope
+  --> $DIR/suggest-let-for-assignment.rs:7:5
+   |
+LL |     x = "x";
+   |     ^
+   |
+help: you might have meant to introduce a new binding
+   |
+LL |     let x = "x";
+   |     +++
+
+error[E0425]: cannot find value `x` in this scope
+  --> $DIR/suggest-let-for-assignment.rs:8:23
+   |
+LL |     println!("x: {}", x);
+   |                       ^ not found in this scope
+
+error[E0425]: cannot find value `x` in this scope
+  --> $DIR/suggest-let-for-assignment.rs:10:8
+   |
+LL |     if x == "x" {
+   |        ^ not found in this scope
+
+error[E0425]: cannot find value `y` in this scope
+  --> $DIR/suggest-let-for-assignment.rs:15:5
+   |
+LL |     y = 1 + 2;
+   |     ^
+   |
+help: you might have meant to introduce a new binding
+   |
+LL |     let y = 1 + 2;
+   |     +++
+
+error[E0425]: cannot find value `y` in this scope
+  --> $DIR/suggest-let-for-assignment.rs:16:23
+   |
+LL |     println!("y: {}", y);
+   |                       ^ not found in this scope
+
+error: aborting due to 7 previous errors
+
+For more information about this error, try `rustc --explain E0425`.
diff --git a/src/tools/tidy/src/ui_tests.rs b/src/tools/tidy/src/ui_tests.rs
index 3815259c9aa..c600f99c2c4 100644
--- a/src/tools/tidy/src/ui_tests.rs
+++ b/src/tools/tidy/src/ui_tests.rs
@@ -8,7 +8,7 @@ use std::path::Path;
 const ENTRY_LIMIT: usize = 1000;
 // FIXME: The following limits should be reduced eventually.
 const ROOT_ENTRY_LIMIT: usize = 948;
-const ISSUES_ENTRY_LIMIT: usize = 2126;
+const ISSUES_ENTRY_LIMIT: usize = 2117;
 
 fn check_entries(path: &Path, bad: &mut bool) {
     let dirs = walkdir::WalkDir::new(&path.join("test/ui"))