about summary refs log tree commit diff
diff options
context:
space:
mode:
-rw-r--r--compiler/rustc_builtin_macros/src/format.rs17
-rw-r--r--compiler/rustc_lint/src/context.rs19
-rw-r--r--compiler/rustc_lint_defs/src/lib.rs2
-rw-r--r--compiler/rustc_middle/src/traits/mod.rs3
-rw-r--r--compiler/rustc_middle/src/ty/mod.rs18
-rw-r--r--compiler/rustc_trait_selection/src/traits/error_reporting/mod.rs1
-rw-r--r--compiler/rustc_trait_selection/src/traits/error_reporting/suggestions.rs28
-rw-r--r--compiler/rustc_typeck/src/check/expr.rs10
-rw-r--r--compiler/rustc_typeck/src/check/fn_ctxt/_impl.rs1
-rw-r--r--compiler/rustc_typeck/src/check/method/mod.rs30
-rw-r--r--compiler/rustc_typeck/src/check/op.rs85
-rw-r--r--src/test/ui/generic-associated-types/missing-bounds.fixed46
-rw-r--r--src/test/ui/generic-associated-types/missing-bounds.rs2
-rw-r--r--src/test/ui/generic-associated-types/missing-bounds.stderr20
-rw-r--r--src/test/ui/macros/issue-99261.rs17
-rw-r--r--src/test/ui/suggestions/issue-97677.fixed8
-rw-r--r--src/test/ui/suggestions/issue-97677.rs4
-rw-r--r--src/test/ui/suggestions/issue-97677.stderr6
18 files changed, 254 insertions, 63 deletions
diff --git a/compiler/rustc_builtin_macros/src/format.rs b/compiler/rustc_builtin_macros/src/format.rs
index 4791151c7d3..3b7e2102ffa 100644
--- a/compiler/rustc_builtin_macros/src/format.rs
+++ b/compiler/rustc_builtin_macros/src/format.rs
@@ -16,7 +16,7 @@ use smallvec::SmallVec;
 
 use rustc_lint_defs::builtin::NAMED_ARGUMENTS_USED_POSITIONALLY;
 use rustc_lint_defs::{BufferedEarlyLint, BuiltinLintDiagnostics, LintId};
-use rustc_parse_format::{Count, FormatSpec};
+use rustc_parse_format::Count;
 use std::borrow::Cow;
 use std::collections::hash_map::Entry;
 
@@ -985,20 +985,19 @@ fn lint_named_arguments_used_positionally(
                 }
                 _ => {}
             };
-            match a.format {
-                FormatSpec { width: Count::CountIsName(s, _), .. }
-                | FormatSpec { precision: Count::CountIsName(s, _), .. } => {
-                    used_argument_names.insert(s);
-                }
-                _ => {}
-            };
+            if let Count::CountIsName(s, _) = a.format.width {
+                used_argument_names.insert(s);
+            }
+            if let Count::CountIsName(s, _) = a.format.precision {
+                used_argument_names.insert(s);
+            }
         }
     }
 
     for (symbol, (index, span)) in names {
         if !used_argument_names.contains(symbol.as_str()) {
             let msg = format!("named argument `{}` is not used by name", symbol.as_str());
-            let arg_span = cx.arg_spans[index];
+            let arg_span = cx.arg_spans.get(index).copied();
             cx.ecx.buffered_early_lint.push(BufferedEarlyLint {
                 span: MultiSpan::from_span(span),
                 msg: msg.clone(),
diff --git a/compiler/rustc_lint/src/context.rs b/compiler/rustc_lint/src/context.rs
index 50e9383cacc..b4b472fe2df 100644
--- a/compiler/rustc_lint/src/context.rs
+++ b/compiler/rustc_lint/src/context.rs
@@ -858,15 +858,16 @@ pub trait LintContext: Sized {
                 },
                 BuiltinLintDiagnostics::NamedArgumentUsedPositionally(positional_arg, named_arg, name) => {
                     db.span_label(named_arg, "this named argument is only referred to by position in formatting string");
-                    let msg = format!("this formatting argument uses named argument `{}` by position", name);
-                    db.span_label(positional_arg, msg);
-                    db.span_suggestion_verbose(
-                        positional_arg,
-                        "use the named argument by name to avoid ambiguity",
-                        format!("{{{}}}", name),
-                        Applicability::MaybeIncorrect,
-                    );
-
+                    if let Some(positional_arg) = positional_arg {
+                        let msg = format!("this formatting argument uses named argument `{}` by position", name);
+                        db.span_label(positional_arg, msg);
+                            db.span_suggestion_verbose(
+                            positional_arg,
+                            "use the named argument by name to avoid ambiguity",
+                            format!("{{{}}}", name),
+                            Applicability::MaybeIncorrect,
+                        );
+                    }
                 }
             }
             // Rewrap `db`, and pass control to the user.
diff --git a/compiler/rustc_lint_defs/src/lib.rs b/compiler/rustc_lint_defs/src/lib.rs
index 1bc7e7de660..4fd57ed8533 100644
--- a/compiler/rustc_lint_defs/src/lib.rs
+++ b/compiler/rustc_lint_defs/src/lib.rs
@@ -467,7 +467,7 @@ pub enum BuiltinLintDiagnostics {
         /// If true, the lifetime will be fully elided.
         use_span: Option<(Span, bool)>,
     },
-    NamedArgumentUsedPositionally(Span, Span, String),
+    NamedArgumentUsedPositionally(Option<Span>, Span, String),
 }
 
 /// Lints that are buffered up early on in the `Session` before the
diff --git a/compiler/rustc_middle/src/traits/mod.rs b/compiler/rustc_middle/src/traits/mod.rs
index d8cc7d3feb0..945cdfbb0e9 100644
--- a/compiler/rustc_middle/src/traits/mod.rs
+++ b/compiler/rustc_middle/src/traits/mod.rs
@@ -12,7 +12,7 @@ pub mod util;
 use crate::infer::canonical::Canonical;
 use crate::ty::abstract_const::NotConstEvaluatable;
 use crate::ty::subst::SubstsRef;
-use crate::ty::{self, AdtKind, Ty, TyCtxt};
+use crate::ty::{self, AdtKind, Predicate, Ty, TyCtxt};
 
 use rustc_data_structures::sync::Lrc;
 use rustc_errors::{Applicability, Diagnostic};
@@ -414,6 +414,7 @@ pub enum ObligationCauseCode<'tcx> {
     BinOp {
         rhs_span: Option<Span>,
         is_lit: bool,
+        output_pred: Option<Predicate<'tcx>>,
     },
 }
 
diff --git a/compiler/rustc_middle/src/ty/mod.rs b/compiler/rustc_middle/src/ty/mod.rs
index f0acb02933a..3536d946db2 100644
--- a/compiler/rustc_middle/src/ty/mod.rs
+++ b/compiler/rustc_middle/src/ty/mod.rs
@@ -1033,6 +1033,24 @@ impl<'tcx> Predicate<'tcx> {
         }
     }
 
+    pub fn to_opt_poly_projection_pred(self) -> Option<PolyProjectionPredicate<'tcx>> {
+        let predicate = self.kind();
+        match predicate.skip_binder() {
+            PredicateKind::Projection(t) => Some(predicate.rebind(t)),
+            PredicateKind::Trait(..)
+            | PredicateKind::Subtype(..)
+            | PredicateKind::Coerce(..)
+            | PredicateKind::RegionOutlives(..)
+            | PredicateKind::WellFormed(..)
+            | PredicateKind::ObjectSafe(..)
+            | PredicateKind::ClosureKind(..)
+            | PredicateKind::TypeOutlives(..)
+            | PredicateKind::ConstEvaluatable(..)
+            | PredicateKind::ConstEquate(..)
+            | PredicateKind::TypeWellFormedFromEnv(..) => None,
+        }
+    }
+
     pub fn to_opt_type_outlives(self) -> Option<PolyTypeOutlivesPredicate<'tcx>> {
         let predicate = self.kind();
         match predicate.skip_binder() {
diff --git a/compiler/rustc_trait_selection/src/traits/error_reporting/mod.rs b/compiler/rustc_trait_selection/src/traits/error_reporting/mod.rs
index 39fce3cf769..c1af1a05972 100644
--- a/compiler/rustc_trait_selection/src/traits/error_reporting/mod.rs
+++ b/compiler/rustc_trait_selection/src/traits/error_reporting/mod.rs
@@ -665,6 +665,7 @@ impl<'a, 'tcx> InferCtxtExt<'tcx> for InferCtxt<'a, 'tcx> {
                             self.suggest_restricting_param_bound(
                                 &mut err,
                                 trait_predicate,
+                                None,
                                 obligation.cause.body_id,
                             );
                         } else if !suggested {
diff --git a/compiler/rustc_trait_selection/src/traits/error_reporting/suggestions.rs b/compiler/rustc_trait_selection/src/traits/error_reporting/suggestions.rs
index 57d5e5436a6..bca80e7ab8a 100644
--- a/compiler/rustc_trait_selection/src/traits/error_reporting/suggestions.rs
+++ b/compiler/rustc_trait_selection/src/traits/error_reporting/suggestions.rs
@@ -24,7 +24,8 @@ use rustc_middle::hir::map;
 use rustc_middle::ty::{
     self, suggest_arbitrary_trait_bound, suggest_constraining_type_param, AdtKind, DefIdTree,
     GeneratorDiagnosticData, GeneratorInteriorTypeCause, Infer, InferTy, IsSuggestable,
-    ToPredicate, Ty, TyCtxt, TypeFoldable, TypeFolder, TypeSuperFoldable, TypeVisitable,
+    ProjectionPredicate, ToPredicate, Ty, TyCtxt, TypeFoldable, TypeFolder, TypeSuperFoldable,
+    TypeVisitable,
 };
 use rustc_middle::ty::{TypeAndMut, TypeckResults};
 use rustc_session::Limit;
@@ -172,6 +173,7 @@ pub trait InferCtxtExt<'tcx> {
         &self,
         err: &mut Diagnostic,
         trait_pred: ty::PolyTraitPredicate<'tcx>,
+        proj_pred: Option<ty::PolyProjectionPredicate<'tcx>>,
         body_id: hir::HirId,
     );
 
@@ -457,6 +459,7 @@ impl<'a, 'tcx> InferCtxtExt<'tcx> for InferCtxt<'a, 'tcx> {
         &self,
         mut err: &mut Diagnostic,
         trait_pred: ty::PolyTraitPredicate<'tcx>,
+        proj_pred: Option<ty::PolyProjectionPredicate<'tcx>>,
         body_id: hir::HirId,
     ) {
         let trait_pred = self.resolve_numeric_literals_with_default(trait_pred);
@@ -589,9 +592,28 @@ impl<'a, 'tcx> InferCtxtExt<'tcx> for InferCtxt<'a, 'tcx> {
                     }
                     // Missing generic type parameter bound.
                     let param_name = self_ty.to_string();
-                    let constraint = with_no_trimmed_paths!(
+                    let mut constraint = with_no_trimmed_paths!(
                         trait_pred.print_modifiers_and_trait_path().to_string()
                     );
+
+                    if let Some(proj_pred) = proj_pred {
+                        let ProjectionPredicate { projection_ty, term } = proj_pred.skip_binder();
+                        let item = self.tcx.associated_item(projection_ty.item_def_id);
+
+                        // FIXME: this case overlaps with code in TyCtxt::note_and_explain_type_err.
+                        // That should be extracted into a helper function.
+                        if constraint.ends_with('>') {
+                            constraint = format!(
+                                "{}, {}={}>",
+                                &constraint[..constraint.len() - 1],
+                                item.name,
+                                term.to_string()
+                            );
+                        } else {
+                            constraint.push_str(&format!("<{}={}>", item.name, term.to_string()));
+                        }
+                    }
+
                     if suggest_constraining_type_param(
                         self.tcx,
                         generics,
@@ -2825,7 +2847,7 @@ impl<'a, 'tcx> InferCtxtExt<'tcx> for InferCtxt<'a, 'tcx> {
         trait_ref: &ty::PolyTraitRef<'tcx>,
     ) {
         let rhs_span = match obligation.cause.code() {
-            ObligationCauseCode::BinOp { rhs_span: Some(span), is_lit } if *is_lit => span,
+            ObligationCauseCode::BinOp { rhs_span: Some(span), is_lit, .. } if *is_lit => span,
             _ => return,
         };
         match (
diff --git a/compiler/rustc_typeck/src/check/expr.rs b/compiler/rustc_typeck/src/check/expr.rs
index 2400211394e..b52cb8e99d1 100644
--- a/compiler/rustc_typeck/src/check/expr.rs
+++ b/compiler/rustc_typeck/src/check/expr.rs
@@ -282,11 +282,13 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> {
         match expr.kind {
             ExprKind::Box(subexpr) => self.check_expr_box(subexpr, expected),
             ExprKind::Lit(ref lit) => self.check_lit(&lit, expected),
-            ExprKind::Binary(op, lhs, rhs) => self.check_binop(expr, op, lhs, rhs),
+            ExprKind::Binary(op, lhs, rhs) => self.check_binop(expr, op, lhs, rhs, expected),
             ExprKind::Assign(lhs, rhs, span) => {
                 self.check_expr_assign(expr, expected, lhs, rhs, span)
             }
-            ExprKind::AssignOp(op, lhs, rhs) => self.check_binop_assign(expr, op, lhs, rhs),
+            ExprKind::AssignOp(op, lhs, rhs) => {
+                self.check_binop_assign(expr, op, lhs, rhs, expected)
+            }
             ExprKind::Unary(unop, oprnd) => self.check_expr_unary(unop, oprnd, expected, expr),
             ExprKind::AddrOf(kind, mutbl, oprnd) => {
                 self.check_expr_addr_of(kind, mutbl, oprnd, expected, expr)
@@ -404,14 +406,14 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> {
                     }
                 }
                 hir::UnOp::Not => {
-                    let result = self.check_user_unop(expr, oprnd_t, unop);
+                    let result = self.check_user_unop(expr, oprnd_t, unop, expected_inner);
                     // If it's builtin, we can reuse the type, this helps inference.
                     if !(oprnd_t.is_integral() || *oprnd_t.kind() == ty::Bool) {
                         oprnd_t = result;
                     }
                 }
                 hir::UnOp::Neg => {
-                    let result = self.check_user_unop(expr, oprnd_t, unop);
+                    let result = self.check_user_unop(expr, oprnd_t, unop, expected_inner);
                     // If it's builtin, we can reuse the type, this helps inference.
                     if !oprnd_t.is_numeric() {
                         oprnd_t = result;
diff --git a/compiler/rustc_typeck/src/check/fn_ctxt/_impl.rs b/compiler/rustc_typeck/src/check/fn_ctxt/_impl.rs
index 42a9a23559c..d33b5b21403 100644
--- a/compiler/rustc_typeck/src/check/fn_ctxt/_impl.rs
+++ b/compiler/rustc_typeck/src/check/fn_ctxt/_impl.rs
@@ -413,6 +413,7 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> {
                     rhs_span: opt_input_expr.map(|expr| expr.span),
                     is_lit: opt_input_expr
                         .map_or(false, |expr| matches!(expr.kind, ExprKind::Lit(_))),
+                    output_pred: None,
                 },
             ),
             self.param_env,
diff --git a/compiler/rustc_typeck/src/check/method/mod.rs b/compiler/rustc_typeck/src/check/method/mod.rs
index c0b3a23fde4..c09f63f1e8f 100644
--- a/compiler/rustc_typeck/src/check/method/mod.rs
+++ b/compiler/rustc_typeck/src/check/method/mod.rs
@@ -10,7 +10,7 @@ mod suggest;
 pub use self::suggest::SelfSource;
 pub use self::MethodError::*;
 
-use crate::check::FnCtxt;
+use crate::check::{Expectation, FnCtxt};
 use crate::ObligationCause;
 use rustc_data_structures::sync::Lrc;
 use rustc_errors::{Applicability, Diagnostic};
@@ -20,8 +20,10 @@ use rustc_hir::def_id::DefId;
 use rustc_infer::infer::{self, InferOk};
 use rustc_middle::ty::subst::Subst;
 use rustc_middle::ty::subst::{InternalSubsts, SubstsRef};
-use rustc_middle::ty::{self, ToPredicate, Ty, TypeVisitable};
-use rustc_middle::ty::{DefIdTree, GenericParamDefKind};
+use rustc_middle::ty::{
+    self, AssocKind, DefIdTree, GenericParamDefKind, ProjectionPredicate, ProjectionTy, Term,
+    ToPredicate, Ty, TypeVisitable,
+};
 use rustc_span::symbol::Ident;
 use rustc_span::Span;
 use rustc_trait_selection::traits;
@@ -318,6 +320,7 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> {
         self_ty: Ty<'tcx>,
         opt_input_type: Option<Ty<'tcx>>,
         opt_input_expr: Option<&'tcx hir::Expr<'tcx>>,
+        expected: Expectation<'tcx>,
     ) -> (traits::Obligation<'tcx, ty::Predicate<'tcx>>, &'tcx ty::List<ty::subst::GenericArg<'tcx>>)
     {
         // Construct a trait-reference `self_ty : Trait<input_tys>`
@@ -339,6 +342,23 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> {
 
         // Construct an obligation
         let poly_trait_ref = ty::Binder::dummy(trait_ref);
+        let opt_output_ty =
+            expected.only_has_type(self).and_then(|ty| (!ty.needs_infer()).then(|| ty));
+        let opt_output_assoc_item = self.tcx.associated_items(trait_def_id).find_by_name_and_kind(
+            self.tcx,
+            Ident::from_str("Output"),
+            AssocKind::Type,
+            trait_def_id,
+        );
+        let output_pred =
+            opt_output_ty.zip(opt_output_assoc_item).map(|(output_ty, output_assoc_item)| {
+                ty::Binder::dummy(ty::PredicateKind::Projection(ProjectionPredicate {
+                    projection_ty: ProjectionTy { substs, item_def_id: output_assoc_item.def_id },
+                    term: Term::Ty(output_ty),
+                }))
+                .to_predicate(self.tcx)
+            });
+
         (
             traits::Obligation::new(
                 traits::ObligationCause::new(
@@ -348,6 +368,7 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> {
                         rhs_span: opt_input_expr.map(|expr| expr.span),
                         is_lit: opt_input_expr
                             .map_or(false, |expr| matches!(expr.kind, hir::ExprKind::Lit(_))),
+                        output_pred,
                     },
                 ),
                 self.param_env,
@@ -397,6 +418,7 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> {
         self_ty: Ty<'tcx>,
         opt_input_type: Option<Ty<'tcx>>,
         opt_input_expr: Option<&'tcx hir::Expr<'tcx>>,
+        expected: Expectation<'tcx>,
     ) -> Option<InferOk<'tcx, MethodCallee<'tcx>>> {
         let (obligation, substs) = self.obligation_for_op_method(
             span,
@@ -404,6 +426,7 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> {
             self_ty,
             opt_input_type,
             opt_input_expr,
+            expected,
         );
         self.construct_obligation_for_trait(
             span,
@@ -505,6 +528,7 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> {
                     rhs_span: opt_input_expr.map(|expr| expr.span),
                     is_lit: opt_input_expr
                         .map_or(false, |expr| matches!(expr.kind, hir::ExprKind::Lit(_))),
+                    output_pred: None,
                 },
             )
         } else {
diff --git a/compiler/rustc_typeck/src/check/op.rs b/compiler/rustc_typeck/src/check/op.rs
index 0887c27ea36..d4bb3d43eff 100644
--- a/compiler/rustc_typeck/src/check/op.rs
+++ b/compiler/rustc_typeck/src/check/op.rs
@@ -2,10 +2,12 @@
 
 use super::method::MethodCallee;
 use super::{has_expected_num_generic_args, FnCtxt};
+use crate::check::Expectation;
 use rustc_ast as ast;
 use rustc_errors::{self, struct_span_err, Applicability, Diagnostic};
 use rustc_hir as hir;
 use rustc_infer::infer::type_variable::{TypeVariableOrigin, TypeVariableOriginKind};
+use rustc_infer::traits::ObligationCauseCode;
 use rustc_middle::ty::adjustment::{
     Adjust, Adjustment, AllowTwoPhase, AutoBorrow, AutoBorrowMutability,
 };
@@ -30,9 +32,10 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> {
         op: hir::BinOp,
         lhs: &'tcx hir::Expr<'tcx>,
         rhs: &'tcx hir::Expr<'tcx>,
+        expected: Expectation<'tcx>,
     ) -> Ty<'tcx> {
         let (lhs_ty, rhs_ty, return_ty) =
-            self.check_overloaded_binop(expr, lhs, rhs, op, IsAssign::Yes);
+            self.check_overloaded_binop(expr, lhs, rhs, op, IsAssign::Yes, expected);
 
         let ty =
             if !lhs_ty.is_ty_var() && !rhs_ty.is_ty_var() && is_builtin_binop(lhs_ty, rhs_ty, op) {
@@ -50,6 +53,7 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> {
                         Some(rhs_ty),
                         Some(rhs),
                         Op::Binary(op, IsAssign::Yes),
+                        expected,
                     )
                     .is_ok()
                 {
@@ -70,6 +74,7 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> {
         op: hir::BinOp,
         lhs_expr: &'tcx hir::Expr<'tcx>,
         rhs_expr: &'tcx hir::Expr<'tcx>,
+        expected: Expectation<'tcx>,
     ) -> Ty<'tcx> {
         let tcx = self.tcx;
 
@@ -94,8 +99,14 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> {
                 // Otherwise, we always treat operators as if they are
                 // overloaded. This is the way to be most flexible w/r/t
                 // types that get inferred.
-                let (lhs_ty, rhs_ty, return_ty) =
-                    self.check_overloaded_binop(expr, lhs_expr, rhs_expr, op, IsAssign::No);
+                let (lhs_ty, rhs_ty, return_ty) = self.check_overloaded_binop(
+                    expr,
+                    lhs_expr,
+                    rhs_expr,
+                    op,
+                    IsAssign::No,
+                    expected,
+                );
 
                 // Supply type inference hints if relevant. Probably these
                 // hints should be enforced during select as part of the
@@ -176,6 +187,7 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> {
         rhs_expr: &'tcx hir::Expr<'tcx>,
         op: hir::BinOp,
         is_assign: IsAssign,
+        expected: Expectation<'tcx>,
     ) -> (Ty<'tcx>, Ty<'tcx>, Ty<'tcx>) {
         debug!(
             "check_overloaded_binop(expr.hir_id={}, op={:?}, is_assign={:?})",
@@ -222,6 +234,7 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> {
             Some(rhs_ty_var),
             Some(rhs_expr),
             Op::Binary(op, is_assign),
+            expected,
         );
 
         // see `NB` above
@@ -282,7 +295,7 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> {
             Err(_) if lhs_ty.references_error() || rhs_ty.references_error() => self.tcx.ty_error(),
             Err(errors) => {
                 let source_map = self.tcx.sess.source_map();
-                let (mut err, missing_trait, _use_output) = match is_assign {
+                let (mut err, missing_trait, use_output) = match is_assign {
                     IsAssign::Yes => {
                         let mut err = struct_span_err!(
                             self.tcx.sess,
@@ -406,6 +419,7 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> {
                                 rhs_expr,
                                 op,
                                 is_assign,
+                                expected,
                             );
                             self.add_type_neq_err_label(
                                 &mut err,
@@ -415,6 +429,7 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> {
                                 lhs_expr,
                                 op,
                                 is_assign,
+                                expected,
                             );
                         }
                         self.note_unmet_impls_on_type(&mut err, errors);
@@ -429,6 +444,7 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> {
                             Some(rhs_ty),
                             Some(rhs_expr),
                             Op::Binary(op, is_assign),
+                            expected,
                         )
                         .is_ok()
                     {
@@ -490,19 +506,31 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> {
                                 Some(rhs_ty),
                                 Some(rhs_expr),
                                 Op::Binary(op, is_assign),
+                                expected,
                             )
                             .unwrap_err();
-                        let predicates = errors
-                            .into_iter()
-                            .filter_map(|error| error.obligation.predicate.to_opt_poly_trait_pred())
-                            .collect::<Vec<_>>();
-                        if !predicates.is_empty() {
-                            for pred in predicates {
-                                self.infcx.suggest_restricting_param_bound(
-                                    &mut err,
-                                    pred,
-                                    self.body_id,
-                                );
+                        if !errors.is_empty() {
+                            for error in errors {
+                                if let Some(trait_pred) =
+                                    error.obligation.predicate.to_opt_poly_trait_pred()
+                                {
+                                    let proj_pred = match error.obligation.cause.code() {
+                                        ObligationCauseCode::BinOp {
+                                            output_pred: Some(output_pred),
+                                            ..
+                                        } if use_output => {
+                                            output_pred.to_opt_poly_projection_pred()
+                                        }
+                                        _ => None,
+                                    };
+
+                                    self.infcx.suggest_restricting_param_bound(
+                                        &mut err,
+                                        trait_pred,
+                                        proj_pred,
+                                        self.body_id,
+                                    );
+                                }
                             }
                         } else if *ty != lhs_ty {
                             // When we know that a missing bound is responsible, we don't show
@@ -532,6 +560,7 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> {
         other_expr: &'tcx hir::Expr<'tcx>,
         op: hir::BinOp,
         is_assign: IsAssign,
+        expected: Expectation<'tcx>,
     ) -> bool /* did we suggest to call a function because of missing parentheses? */ {
         err.span_label(span, ty.to_string());
         if let FnDef(def_id, _) = *ty.kind() {
@@ -561,6 +590,7 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> {
                     Some(other_ty),
                     Some(other_expr),
                     Op::Binary(op, is_assign),
+                    expected,
                 )
                 .is_ok()
             {
@@ -677,9 +707,10 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> {
         ex: &'tcx hir::Expr<'tcx>,
         operand_ty: Ty<'tcx>,
         op: hir::UnOp,
+        expected: Expectation<'tcx>,
     ) -> Ty<'tcx> {
         assert!(op.is_by_value());
-        match self.lookup_op_method(operand_ty, None, None, Op::Unary(op, ex.span)) {
+        match self.lookup_op_method(operand_ty, None, None, Op::Unary(op, ex.span), expected) {
             Ok(method) => {
                 self.write_method_call(ex.hir_id, method);
                 method.sig.output()
@@ -712,6 +743,7 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> {
                             self.infcx.suggest_restricting_param_bound(
                                 &mut err,
                                 pred,
+                                None,
                                 self.body_id,
                             );
                         }
@@ -772,6 +804,7 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> {
         other_ty: Option<Ty<'tcx>>,
         other_ty_expr: Option<&'tcx hir::Expr<'tcx>>,
         op: Op,
+        expected: Expectation<'tcx>,
     ) -> Result<MethodCallee<'tcx>, Vec<FulfillmentError<'tcx>>> {
         let lang = self.tcx.lang_items();
 
@@ -856,7 +889,15 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> {
 
         let opname = Ident::with_dummy_span(opname);
         let method = trait_did.and_then(|trait_did| {
-            self.lookup_op_method_in_trait(span, opname, trait_did, lhs_ty, other_ty, other_ty_expr)
+            self.lookup_op_method_in_trait(
+                span,
+                opname,
+                trait_did,
+                lhs_ty,
+                other_ty,
+                other_ty_expr,
+                expected,
+            )
         });
 
         match (method, trait_did) {
@@ -867,8 +908,14 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> {
             }
             (None, None) => Err(vec![]),
             (None, Some(trait_did)) => {
-                let (obligation, _) =
-                    self.obligation_for_op_method(span, trait_did, lhs_ty, other_ty, other_ty_expr);
+                let (obligation, _) = self.obligation_for_op_method(
+                    span,
+                    trait_did,
+                    lhs_ty,
+                    other_ty,
+                    other_ty_expr,
+                    expected,
+                );
                 let mut fulfill = <dyn TraitEngine<'_>>::new(self.tcx);
                 fulfill.register_predicate_obligation(self, obligation);
                 Err(fulfill.select_where_possible(&self.infcx))
diff --git a/src/test/ui/generic-associated-types/missing-bounds.fixed b/src/test/ui/generic-associated-types/missing-bounds.fixed
new file mode 100644
index 00000000000..2315810a47a
--- /dev/null
+++ b/src/test/ui/generic-associated-types/missing-bounds.fixed
@@ -0,0 +1,46 @@
+// run-rustfix
+
+use std::ops::Add;
+
+struct A<B>(B);
+
+impl<B> Add for A<B> where B: Add + Add<Output = B> {
+    type Output = Self;
+
+    fn add(self, rhs: Self) -> Self {
+        A(self.0 + rhs.0) //~ ERROR mismatched types
+    }
+}
+
+struct C<B>(B);
+
+impl<B: Add + Add<Output = B>> Add for C<B> {
+    type Output = Self;
+
+    fn add(self, rhs: Self) -> Self {
+        Self(self.0 + rhs.0) //~ ERROR mismatched types
+    }
+}
+
+struct D<B>(B);
+
+impl<B: std::ops::Add<Output=B>> Add for D<B> {
+    type Output = Self;
+
+    fn add(self, rhs: Self) -> Self {
+        Self(self.0 + rhs.0) //~ ERROR cannot add `B` to `B`
+    }
+}
+
+struct E<B>(B);
+
+impl<B: Add + Add<Output = B>> Add for E<B> where B: Add<Output = B> {
+    //~^ ERROR equality constraints are not yet supported in `where` clauses
+    type Output = Self;
+
+    fn add(self, rhs: Self) -> Self {
+        Self(self.0 + rhs.0) //~ ERROR mismatched types
+    }
+}
+
+fn main() {}
diff --git a/src/test/ui/generic-associated-types/missing-bounds.rs b/src/test/ui/generic-associated-types/missing-bounds.rs
index b3661ba3744..ffafff5e9f5 100644
--- a/src/test/ui/generic-associated-types/missing-bounds.rs
+++ b/src/test/ui/generic-associated-types/missing-bounds.rs
@@ -1,3 +1,5 @@
+// run-rustfix
+
 use std::ops::Add;
 
 struct A<B>(B);
diff --git a/src/test/ui/generic-associated-types/missing-bounds.stderr b/src/test/ui/generic-associated-types/missing-bounds.stderr
index 5323ee17226..138c642dd79 100644
--- a/src/test/ui/generic-associated-types/missing-bounds.stderr
+++ b/src/test/ui/generic-associated-types/missing-bounds.stderr
@@ -1,5 +1,5 @@
 error: equality constraints are not yet supported in `where` clauses
-  --> $DIR/missing-bounds.rs:35:33
+  --> $DIR/missing-bounds.rs:37:33
    |
 LL | impl<B: Add> Add for E<B> where <B as Add>::Output = B {
    |                                 ^^^^^^^^^^^^^^^^^^^^^^ not supported
@@ -11,7 +11,7 @@ LL | impl<B: Add> Add for E<B> where B: Add<Output = B> {
    |                                 ~~~~~~~~~~~~~~~~~~
 
 error[E0308]: mismatched types
-  --> $DIR/missing-bounds.rs:9:11
+  --> $DIR/missing-bounds.rs:11:11
    |
 LL | impl<B> Add for A<B> where B: Add {
    |      - this type parameter
@@ -24,7 +24,7 @@ LL |         A(self.0 + rhs.0)
    = note: expected type parameter `B`
              found associated type `<B as Add>::Output`
 note: tuple struct defined here
-  --> $DIR/missing-bounds.rs:3:8
+  --> $DIR/missing-bounds.rs:5:8
    |
 LL | struct A<B>(B);
    |        ^
@@ -34,7 +34,7 @@ LL | impl<B> Add for A<B> where B: Add + Add<Output = B> {
    |                                   +++++++++++++++++
 
 error[E0308]: mismatched types
-  --> $DIR/missing-bounds.rs:19:14
+  --> $DIR/missing-bounds.rs:21:14
    |
 LL | impl<B: Add> Add for C<B> {
    |      - this type parameter
@@ -47,7 +47,7 @@ LL |         Self(self.0 + rhs.0)
    = note: expected type parameter `B`
              found associated type `<B as Add>::Output`
 note: tuple struct defined here
-  --> $DIR/missing-bounds.rs:13:8
+  --> $DIR/missing-bounds.rs:15:8
    |
 LL | struct C<B>(B);
    |        ^
@@ -57,7 +57,7 @@ LL | impl<B: Add + Add<Output = B>> Add for C<B> {
    |             +++++++++++++++++
 
 error[E0369]: cannot add `B` to `B`
-  --> $DIR/missing-bounds.rs:29:21
+  --> $DIR/missing-bounds.rs:31:21
    |
 LL |         Self(self.0 + rhs.0)
    |              ------ ^ ----- B
@@ -66,11 +66,11 @@ LL |         Self(self.0 + rhs.0)
    |
 help: consider restricting type parameter `B`
    |
-LL | impl<B: std::ops::Add> Add for D<B> {
-   |       +++++++++++++++
+LL | impl<B: std::ops::Add<Output=B>> Add for D<B> {
+   |       +++++++++++++++++++++++++
 
 error[E0308]: mismatched types
-  --> $DIR/missing-bounds.rs:40:14
+  --> $DIR/missing-bounds.rs:42:14
    |
 LL | impl<B: Add> Add for E<B> where <B as Add>::Output = B {
    |      - this type parameter
@@ -83,7 +83,7 @@ LL |         Self(self.0 + rhs.0)
    = note: expected type parameter `B`
              found associated type `<B as Add>::Output`
 note: tuple struct defined here
-  --> $DIR/missing-bounds.rs:33:8
+  --> $DIR/missing-bounds.rs:35:8
    |
 LL | struct E<B>(B);
    |        ^
diff --git a/src/test/ui/macros/issue-99261.rs b/src/test/ui/macros/issue-99261.rs
new file mode 100644
index 00000000000..40d26d08cba
--- /dev/null
+++ b/src/test/ui/macros/issue-99261.rs
@@ -0,0 +1,17 @@
+// check-pass
+
+#![deny(named_arguments_used_positionally)]
+
+fn main() {
+    let value: f64 = 314.15926;
+    let digits_before_decimal = 1;
+    let digits_after_decimal = 2;
+    let width = digits_before_decimal + 1 + digits_after_decimal;
+
+    println!(
+        "{value:0>width$.digits_after_decimal$}",
+        value = value,
+        width = width,
+        digits_after_decimal = digits_after_decimal,
+    );
+}
diff --git a/src/test/ui/suggestions/issue-97677.fixed b/src/test/ui/suggestions/issue-97677.fixed
new file mode 100644
index 00000000000..73ca9f97b43
--- /dev/null
+++ b/src/test/ui/suggestions/issue-97677.fixed
@@ -0,0 +1,8 @@
+// run-rustfix
+
+fn add_ten<N: std::ops::Add<i32, Output=N>>(n: N) -> N {
+    n + 10
+    //~^ ERROR cannot add `{integer}` to `N`
+}
+
+fn main() { add_ten(0); }
diff --git a/src/test/ui/suggestions/issue-97677.rs b/src/test/ui/suggestions/issue-97677.rs
index a4c3b1350b8..2abf2af3384 100644
--- a/src/test/ui/suggestions/issue-97677.rs
+++ b/src/test/ui/suggestions/issue-97677.rs
@@ -1,6 +1,8 @@
+// run-rustfix
+
 fn add_ten<N>(n: N) -> N {
     n + 10
     //~^ ERROR cannot add `{integer}` to `N`
 }
 
-fn main() {}
+fn main() { add_ten(0); }
diff --git a/src/test/ui/suggestions/issue-97677.stderr b/src/test/ui/suggestions/issue-97677.stderr
index ea563ea844d..069b184ac63 100644
--- a/src/test/ui/suggestions/issue-97677.stderr
+++ b/src/test/ui/suggestions/issue-97677.stderr
@@ -1,5 +1,5 @@
 error[E0369]: cannot add `{integer}` to `N`
-  --> $DIR/issue-97677.rs:2:7
+  --> $DIR/issue-97677.rs:4:7
    |
 LL |     n + 10
    |     - ^ -- {integer}
@@ -8,8 +8,8 @@ LL |     n + 10
    |
 help: consider restricting type parameter `N`
    |
-LL | fn add_ten<N: std::ops::Add<i32>>(n: N) -> N {
-   |             ++++++++++++++++++++
+LL | fn add_ten<N: std::ops::Add<i32, Output=N>>(n: N) -> N {
+   |             ++++++++++++++++++++++++++++++
 
 error: aborting due to previous error