rustc_hir_typeck/
op.rs

1//! Code related to processing overloaded binary and unary operators.
2
3use rustc_data_structures::packed::Pu128;
4use rustc_errors::codes::*;
5use rustc_errors::{Applicability, Diag, struct_span_code_err};
6use rustc_infer::traits::ObligationCauseCode;
7use rustc_middle::bug;
8use rustc_middle::ty::adjustment::{
9    Adjust, Adjustment, AllowTwoPhase, AutoBorrow, AutoBorrowMutability,
10};
11use rustc_middle::ty::print::with_no_trimmed_paths;
12use rustc_middle::ty::{self, IsSuggestable, Ty, TyCtxt, TypeVisitableExt};
13use rustc_session::errors::ExprParenthesesNeeded;
14use rustc_span::source_map::Spanned;
15use rustc_span::{Span, Symbol, sym};
16use rustc_trait_selection::infer::InferCtxtExt;
17use rustc_trait_selection::traits::{FulfillmentError, Obligation, ObligationCtxt};
18use tracing::debug;
19use {rustc_ast as ast, rustc_hir as hir};
20
21use super::FnCtxt;
22use super::method::MethodCallee;
23use crate::Expectation;
24
25impl<'a, 'tcx> FnCtxt<'a, 'tcx> {
26    /// Checks a `a <op>= b`
27    pub(crate) fn check_expr_assign_op(
28        &self,
29        expr: &'tcx hir::Expr<'tcx>,
30        op: hir::AssignOp,
31        lhs: &'tcx hir::Expr<'tcx>,
32        rhs: &'tcx hir::Expr<'tcx>,
33        expected: Expectation<'tcx>,
34    ) -> Ty<'tcx> {
35        let (lhs_ty, rhs_ty, return_ty) =
36            self.check_overloaded_binop(expr, lhs, rhs, Op::AssignOp(op), expected);
37
38        let category = BinOpCategory::from(op.node);
39        let ty = if !lhs_ty.is_ty_var()
40            && !rhs_ty.is_ty_var()
41            && is_builtin_binop(lhs_ty, rhs_ty, category)
42        {
43            self.enforce_builtin_binop_types(lhs.span, lhs_ty, rhs.span, rhs_ty, category);
44            self.tcx.types.unit
45        } else {
46            return_ty
47        };
48
49        self.check_lhs_assignable(lhs, E0067, op.span, |err| {
50            if let Some(lhs_deref_ty) = self.deref_once_mutably_for_diagnostic(lhs_ty) {
51                if self
52                    .lookup_op_method(
53                        (lhs, lhs_deref_ty),
54                        Some((rhs, rhs_ty)),
55                        lang_item_for_binop(self.tcx, Op::AssignOp(op)),
56                        op.span,
57                        expected,
58                    )
59                    .is_ok()
60                {
61                    // If LHS += RHS is an error, but *LHS += RHS is successful, then we will have
62                    // emitted a better suggestion during error handling in check_overloaded_binop.
63                    if self
64                        .lookup_op_method(
65                            (lhs, lhs_ty),
66                            Some((rhs, rhs_ty)),
67                            lang_item_for_binop(self.tcx, Op::AssignOp(op)),
68                            op.span,
69                            expected,
70                        )
71                        .is_err()
72                    {
73                        err.downgrade_to_delayed_bug();
74                    } else {
75                        // Otherwise, it's valid to suggest dereferencing the LHS here.
76                        err.span_suggestion_verbose(
77                            lhs.span.shrink_to_lo(),
78                            "consider dereferencing the left-hand side of this operation",
79                            "*",
80                            Applicability::MaybeIncorrect,
81                        );
82                    }
83                }
84            }
85        });
86
87        ty
88    }
89
90    /// Checks a potentially overloaded binary operator.
91    pub(crate) fn check_expr_binop(
92        &self,
93        expr: &'tcx hir::Expr<'tcx>,
94        op: hir::BinOp,
95        lhs_expr: &'tcx hir::Expr<'tcx>,
96        rhs_expr: &'tcx hir::Expr<'tcx>,
97        expected: Expectation<'tcx>,
98    ) -> Ty<'tcx> {
99        let tcx = self.tcx;
100
101        debug!(
102            "check_binop(expr.hir_id={}, expr={:?}, op={:?}, lhs_expr={:?}, rhs_expr={:?})",
103            expr.hir_id, expr, op, lhs_expr, rhs_expr
104        );
105
106        match BinOpCategory::from(op.node) {
107            BinOpCategory::Shortcircuit => {
108                // && and || are a simple case.
109                self.check_expr_coercible_to_type(lhs_expr, tcx.types.bool, None);
110                let lhs_diverges = self.diverges.get();
111                self.check_expr_coercible_to_type(rhs_expr, tcx.types.bool, None);
112
113                // Depending on the LHS' value, the RHS can never execute.
114                self.diverges.set(lhs_diverges);
115
116                tcx.types.bool
117            }
118            _ => {
119                // Otherwise, we always treat operators as if they are
120                // overloaded. This is the way to be most flexible w/r/t
121                // types that get inferred.
122                let (lhs_ty, rhs_ty, return_ty) =
123                    self.check_overloaded_binop(expr, lhs_expr, rhs_expr, Op::BinOp(op), expected);
124
125                // Supply type inference hints if relevant. Probably these
126                // hints should be enforced during select as part of the
127                // `consider_unification_despite_ambiguity` routine, but this
128                // more convenient for now.
129                //
130                // The basic idea is to help type inference by taking
131                // advantage of things we know about how the impls for
132                // scalar types are arranged. This is important in a
133                // scenario like `1_u32 << 2`, because it lets us quickly
134                // deduce that the result type should be `u32`, even
135                // though we don't know yet what type 2 has and hence
136                // can't pin this down to a specific impl.
137                let category = BinOpCategory::from(op.node);
138                if !lhs_ty.is_ty_var()
139                    && !rhs_ty.is_ty_var()
140                    && is_builtin_binop(lhs_ty, rhs_ty, category)
141                {
142                    let builtin_return_ty = self.enforce_builtin_binop_types(
143                        lhs_expr.span,
144                        lhs_ty,
145                        rhs_expr.span,
146                        rhs_ty,
147                        category,
148                    );
149                    self.demand_eqtype(expr.span, builtin_return_ty, return_ty);
150                    builtin_return_ty
151                } else {
152                    return_ty
153                }
154            }
155        }
156    }
157
158    fn enforce_builtin_binop_types(
159        &self,
160        lhs_span: Span,
161        lhs_ty: Ty<'tcx>,
162        rhs_span: Span,
163        rhs_ty: Ty<'tcx>,
164        category: BinOpCategory,
165    ) -> Ty<'tcx> {
166        debug_assert!(is_builtin_binop(lhs_ty, rhs_ty, category));
167
168        // Special-case a single layer of referencing, so that things like `5.0 + &6.0f32` work.
169        // (See https://github.com/rust-lang/rust/issues/57447.)
170        let (lhs_ty, rhs_ty) = (deref_ty_if_possible(lhs_ty), deref_ty_if_possible(rhs_ty));
171
172        let tcx = self.tcx;
173        match category {
174            BinOpCategory::Shortcircuit => {
175                self.demand_suptype(lhs_span, tcx.types.bool, lhs_ty);
176                self.demand_suptype(rhs_span, tcx.types.bool, rhs_ty);
177                tcx.types.bool
178            }
179
180            BinOpCategory::Shift => {
181                // result type is same as LHS always
182                lhs_ty
183            }
184
185            BinOpCategory::Math | BinOpCategory::Bitwise => {
186                // both LHS and RHS and result will have the same type
187                self.demand_suptype(rhs_span, lhs_ty, rhs_ty);
188                lhs_ty
189            }
190
191            BinOpCategory::Comparison => {
192                // both LHS and RHS and result will have the same type
193                self.demand_suptype(rhs_span, lhs_ty, rhs_ty);
194                tcx.types.bool
195            }
196        }
197    }
198
199    fn check_overloaded_binop(
200        &self,
201        expr: &'tcx hir::Expr<'tcx>,
202        lhs_expr: &'tcx hir::Expr<'tcx>,
203        rhs_expr: &'tcx hir::Expr<'tcx>,
204        op: Op,
205        expected: Expectation<'tcx>,
206    ) -> (Ty<'tcx>, Ty<'tcx>, Ty<'tcx>) {
207        debug!("check_overloaded_binop(expr.hir_id={}, op={:?})", expr.hir_id, op);
208
209        let lhs_ty = match op {
210            Op::BinOp(_) => {
211                // Find a suitable supertype of the LHS expression's type, by coercing to
212                // a type variable, to pass as the `Self` to the trait, avoiding invariant
213                // trait matching creating lifetime constraints that are too strict.
214                // e.g., adding `&'a T` and `&'b T`, given `&'x T: Add<&'x T>`, will result
215                // in `&'a T <: &'x T` and `&'b T <: &'x T`, instead of `'a = 'b = 'x`.
216                let lhs_ty = self.check_expr(lhs_expr);
217                let fresh_var = self.next_ty_var(lhs_expr.span);
218                self.demand_coerce(lhs_expr, lhs_ty, fresh_var, Some(rhs_expr), AllowTwoPhase::No)
219            }
220            Op::AssignOp(_) => {
221                // rust-lang/rust#52126: We have to use strict
222                // equivalence on the LHS of an assign-op like `+=`;
223                // overwritten or mutably-borrowed places cannot be
224                // coerced to a supertype.
225                self.check_expr(lhs_expr)
226            }
227        };
228        let lhs_ty = self.resolve_vars_with_obligations(lhs_ty);
229
230        // N.B., as we have not yet type-checked the RHS, we don't have the
231        // type at hand. Make a variable to represent it. The whole reason
232        // for this indirection is so that, below, we can check the expr
233        // using this variable as the expected type, which sometimes lets
234        // us do better coercions than we would be able to do otherwise,
235        // particularly for things like `String + &String`.
236        let rhs_ty_var = self.next_ty_var(rhs_expr.span);
237        let result = self.lookup_op_method(
238            (lhs_expr, lhs_ty),
239            Some((rhs_expr, rhs_ty_var)),
240            lang_item_for_binop(self.tcx, op),
241            op.span(),
242            expected,
243        );
244
245        // see `NB` above
246        let rhs_ty = self.check_expr_coercible_to_type_or_error(
247            rhs_expr,
248            rhs_ty_var,
249            Some(lhs_expr),
250            |err, ty| {
251                if let Op::BinOp(binop) = op
252                    && binop.node == hir::BinOpKind::Eq
253                {
254                    self.suggest_swapping_lhs_and_rhs(err, ty, lhs_ty, rhs_expr, lhs_expr);
255                }
256            },
257        );
258        let rhs_ty = self.resolve_vars_with_obligations(rhs_ty);
259
260        let return_ty = match result {
261            Ok(method) => {
262                let by_ref_binop = !op.is_by_value();
263                if matches!(op, Op::AssignOp(_)) || by_ref_binop {
264                    if let ty::Ref(_, _, mutbl) = method.sig.inputs()[0].kind() {
265                        let mutbl = AutoBorrowMutability::new(*mutbl, AllowTwoPhase::Yes);
266                        let autoref = Adjustment {
267                            kind: Adjust::Borrow(AutoBorrow::Ref(mutbl)),
268                            target: method.sig.inputs()[0],
269                        };
270                        self.apply_adjustments(lhs_expr, vec![autoref]);
271                    }
272                }
273                if by_ref_binop {
274                    if let ty::Ref(_, _, mutbl) = method.sig.inputs()[1].kind() {
275                        // Allow two-phase borrows for binops in initial deployment
276                        // since they desugar to methods
277                        let mutbl = AutoBorrowMutability::new(*mutbl, AllowTwoPhase::Yes);
278
279                        let autoref = Adjustment {
280                            kind: Adjust::Borrow(AutoBorrow::Ref(mutbl)),
281                            target: method.sig.inputs()[1],
282                        };
283                        // HACK(eddyb) Bypass checks due to reborrows being in
284                        // some cases applied on the RHS, on top of which we need
285                        // to autoref, which is not allowed by apply_adjustments.
286                        // self.apply_adjustments(rhs_expr, vec![autoref]);
287                        self.typeck_results
288                            .borrow_mut()
289                            .adjustments_mut()
290                            .entry(rhs_expr.hir_id)
291                            .or_default()
292                            .push(autoref);
293                    }
294                }
295                self.write_method_call_and_enforce_effects(expr.hir_id, expr.span, method);
296
297                method.sig.output()
298            }
299            // error types are considered "builtin"
300            Err(_) if lhs_ty.references_error() || rhs_ty.references_error() => {
301                Ty::new_misc_error(self.tcx)
302            }
303            Err(errors) => {
304                let (_, trait_def_id) = lang_item_for_binop(self.tcx, op);
305                let missing_trait = trait_def_id
306                    .map(|def_id| with_no_trimmed_paths!(self.tcx.def_path_str(def_id)));
307                let mut path = None;
308                let lhs_ty_str = self.tcx.short_string(lhs_ty, &mut path);
309                let rhs_ty_str = self.tcx.short_string(rhs_ty, &mut path);
310                let (mut err, output_def_id) = match op {
311                    Op::AssignOp(assign_op) => {
312                        let s = assign_op.node.as_str();
313                        let mut err = struct_span_code_err!(
314                            self.dcx(),
315                            expr.span,
316                            E0368,
317                            "binary assignment operation `{}` cannot be applied to type `{}`",
318                            s,
319                            lhs_ty_str,
320                        );
321                        err.span_label(
322                            lhs_expr.span,
323                            format!("cannot use `{}` on type `{}`", s, lhs_ty_str),
324                        );
325                        self.note_unmet_impls_on_type(&mut err, errors, false);
326                        (err, None)
327                    }
328                    Op::BinOp(bin_op) => {
329                        let message = match bin_op.node {
330                            hir::BinOpKind::Add => {
331                                format!("cannot add `{rhs_ty_str}` to `{lhs_ty_str}`")
332                            }
333                            hir::BinOpKind::Sub => {
334                                format!("cannot subtract `{rhs_ty_str}` from `{lhs_ty_str}`")
335                            }
336                            hir::BinOpKind::Mul => {
337                                format!("cannot multiply `{lhs_ty_str}` by `{rhs_ty_str}`")
338                            }
339                            hir::BinOpKind::Div => {
340                                format!("cannot divide `{lhs_ty_str}` by `{rhs_ty_str}`")
341                            }
342                            hir::BinOpKind::Rem => {
343                                format!(
344                                    "cannot calculate the remainder of `{lhs_ty_str}` divided by \
345                                     `{rhs_ty_str}`"
346                                )
347                            }
348                            hir::BinOpKind::BitAnd => {
349                                format!("no implementation for `{lhs_ty_str} & {rhs_ty_str}`")
350                            }
351                            hir::BinOpKind::BitXor => {
352                                format!("no implementation for `{lhs_ty_str} ^ {rhs_ty_str}`")
353                            }
354                            hir::BinOpKind::BitOr => {
355                                format!("no implementation for `{lhs_ty_str} | {rhs_ty_str}`")
356                            }
357                            hir::BinOpKind::Shl => {
358                                format!("no implementation for `{lhs_ty_str} << {rhs_ty_str}`")
359                            }
360                            hir::BinOpKind::Shr => {
361                                format!("no implementation for `{lhs_ty_str} >> {rhs_ty_str}`")
362                            }
363                            _ => format!(
364                                "binary operation `{}` cannot be applied to type `{}`",
365                                bin_op.node.as_str(),
366                                lhs_ty_str
367                            ),
368                        };
369                        let output_def_id = trait_def_id.and_then(|def_id| {
370                            self.tcx
371                                .associated_item_def_ids(def_id)
372                                .iter()
373                                .find(|item_def_id| {
374                                    self.tcx.associated_item(*item_def_id).name() == sym::Output
375                                })
376                                .cloned()
377                        });
378                        let mut err =
379                            struct_span_code_err!(self.dcx(), bin_op.span, E0369, "{message}");
380                        if !lhs_expr.span.eq(&rhs_expr.span) {
381                            err.span_label(lhs_expr.span, lhs_ty_str.clone());
382                            err.span_label(rhs_expr.span, rhs_ty_str);
383                        }
384                        let suggest_derive = self.can_eq(self.param_env, lhs_ty, rhs_ty);
385                        self.note_unmet_impls_on_type(&mut err, errors, suggest_derive);
386                        (err, output_def_id)
387                    }
388                };
389                *err.long_ty_path() = path;
390
391                // Try to suggest a semicolon if it's `A \n *B` where `B` is a place expr
392                let maybe_missing_semi = self.check_for_missing_semi(expr, &mut err);
393
394                // We defer to the later error produced by `check_lhs_assignable`.
395                // We only downgrade this if it's the LHS, though, and if this is a
396                // valid assignment statement.
397                if maybe_missing_semi
398                    && let hir::Node::Expr(parent) = self.tcx.parent_hir_node(expr.hir_id)
399                    && let hir::ExprKind::Assign(lhs, _, _) = parent.kind
400                    && let hir::Node::Stmt(stmt) = self.tcx.parent_hir_node(parent.hir_id)
401                    && let hir::StmtKind::Expr(_) | hir::StmtKind::Semi(_) = stmt.kind
402                    && lhs.hir_id == expr.hir_id
403                {
404                    err.downgrade_to_delayed_bug();
405                }
406
407                let suggest_deref_binop = |err: &mut Diag<'_, _>, lhs_deref_ty: Ty<'tcx>| {
408                    if self
409                        .lookup_op_method(
410                            (lhs_expr, lhs_deref_ty),
411                            Some((rhs_expr, rhs_ty)),
412                            lang_item_for_binop(self.tcx, op),
413                            op.span(),
414                            expected,
415                        )
416                        .is_ok()
417                    {
418                        let msg = format!(
419                            "`{}` can be used on `{}` if you dereference the left-hand side",
420                            op.as_str(),
421                            self.tcx.short_string(lhs_deref_ty, err.long_ty_path()),
422                        );
423                        err.span_suggestion_verbose(
424                            lhs_expr.span.shrink_to_lo(),
425                            msg,
426                            "*",
427                            rustc_errors::Applicability::MachineApplicable,
428                        );
429                    }
430                };
431
432                let suggest_different_borrow =
433                    |err: &mut Diag<'_, _>,
434                     lhs_adjusted_ty,
435                     lhs_new_mutbl: Option<ast::Mutability>,
436                     rhs_adjusted_ty,
437                     rhs_new_mutbl: Option<ast::Mutability>| {
438                        if self
439                            .lookup_op_method(
440                                (lhs_expr, lhs_adjusted_ty),
441                                Some((rhs_expr, rhs_adjusted_ty)),
442                                lang_item_for_binop(self.tcx, op),
443                                op.span(),
444                                expected,
445                            )
446                            .is_ok()
447                        {
448                            let lhs = self.tcx.short_string(lhs_adjusted_ty, err.long_ty_path());
449                            let rhs = self.tcx.short_string(rhs_adjusted_ty, err.long_ty_path());
450                            let op = op.as_str();
451                            err.note(format!("an implementation for `{lhs} {op} {rhs}` exists"));
452
453                            if let Some(lhs_new_mutbl) = lhs_new_mutbl
454                                && let Some(rhs_new_mutbl) = rhs_new_mutbl
455                                && lhs_new_mutbl.is_not()
456                                && rhs_new_mutbl.is_not()
457                            {
458                                err.multipart_suggestion_verbose(
459                                    "consider reborrowing both sides",
460                                    vec![
461                                        (lhs_expr.span.shrink_to_lo(), "&*".to_string()),
462                                        (rhs_expr.span.shrink_to_lo(), "&*".to_string()),
463                                    ],
464                                    rustc_errors::Applicability::MachineApplicable,
465                                );
466                            } else {
467                                let mut suggest_new_borrow =
468                                    |new_mutbl: ast::Mutability, sp: Span| {
469                                        // Can reborrow (&mut -> &)
470                                        if new_mutbl.is_not() {
471                                            err.span_suggestion_verbose(
472                                                sp.shrink_to_lo(),
473                                                "consider reborrowing this side",
474                                                "&*",
475                                                rustc_errors::Applicability::MachineApplicable,
476                                            );
477                                        // Works on &mut but have &
478                                        } else {
479                                            err.span_help(
480                                                sp,
481                                                "consider making this expression a mutable borrow",
482                                            );
483                                        }
484                                    };
485
486                                if let Some(lhs_new_mutbl) = lhs_new_mutbl {
487                                    suggest_new_borrow(lhs_new_mutbl, lhs_expr.span);
488                                }
489                                if let Some(rhs_new_mutbl) = rhs_new_mutbl {
490                                    suggest_new_borrow(rhs_new_mutbl, rhs_expr.span);
491                                }
492                            }
493                        }
494                    };
495
496                let is_compatible_after_call = |lhs_ty, rhs_ty| {
497                    self.lookup_op_method(
498                        (lhs_expr, lhs_ty),
499                        Some((rhs_expr, rhs_ty)),
500                        lang_item_for_binop(self.tcx, op),
501                        op.span(),
502                        expected,
503                    )
504                    .is_ok()
505                        // Suggest calling even if, after calling, the types don't
506                        // implement the operator, since it'll lead to better
507                        // diagnostics later.
508                        || self.can_eq(self.param_env, lhs_ty, rhs_ty)
509                };
510
511                // We should suggest `a + b` => `*a + b` if `a` is copy, and suggest
512                // `a += b` => `*a += b` if a is a mut ref.
513                if !op.span().can_be_used_for_suggestions() {
514                    // Suppress suggestions when lhs and rhs are not in the same span as the error
515                } else if let Op::AssignOp(_) = op
516                    && let Some(lhs_deref_ty) = self.deref_once_mutably_for_diagnostic(lhs_ty)
517                {
518                    suggest_deref_binop(&mut err, lhs_deref_ty);
519                } else if let Op::BinOp(_) = op
520                    && let ty::Ref(region, lhs_deref_ty, mutbl) = lhs_ty.kind()
521                {
522                    if self.type_is_copy_modulo_regions(self.param_env, *lhs_deref_ty) {
523                        suggest_deref_binop(&mut err, *lhs_deref_ty);
524                    } else {
525                        let lhs_inv_mutbl = mutbl.invert();
526                        let lhs_inv_mutbl_ty =
527                            Ty::new_ref(self.tcx, *region, *lhs_deref_ty, lhs_inv_mutbl);
528
529                        suggest_different_borrow(
530                            &mut err,
531                            lhs_inv_mutbl_ty,
532                            Some(lhs_inv_mutbl),
533                            rhs_ty,
534                            None,
535                        );
536
537                        if let ty::Ref(region, rhs_deref_ty, mutbl) = rhs_ty.kind() {
538                            let rhs_inv_mutbl = mutbl.invert();
539                            let rhs_inv_mutbl_ty =
540                                Ty::new_ref(self.tcx, *region, *rhs_deref_ty, rhs_inv_mutbl);
541
542                            suggest_different_borrow(
543                                &mut err,
544                                lhs_ty,
545                                None,
546                                rhs_inv_mutbl_ty,
547                                Some(rhs_inv_mutbl),
548                            );
549                            suggest_different_borrow(
550                                &mut err,
551                                lhs_inv_mutbl_ty,
552                                Some(lhs_inv_mutbl),
553                                rhs_inv_mutbl_ty,
554                                Some(rhs_inv_mutbl),
555                            );
556                        }
557                    }
558                } else if self.suggest_fn_call(&mut err, lhs_expr, lhs_ty, |lhs_ty| {
559                    is_compatible_after_call(lhs_ty, rhs_ty)
560                }) || self.suggest_fn_call(&mut err, rhs_expr, rhs_ty, |rhs_ty| {
561                    is_compatible_after_call(lhs_ty, rhs_ty)
562                }) || self.suggest_two_fn_call(
563                    &mut err,
564                    rhs_expr,
565                    rhs_ty,
566                    lhs_expr,
567                    lhs_ty,
568                    |lhs_ty, rhs_ty| is_compatible_after_call(lhs_ty, rhs_ty),
569                ) {
570                    // Cool
571                }
572
573                if let Some(missing_trait) = missing_trait {
574                    if matches!(
575                        op,
576                        Op::BinOp(Spanned { node: hir::BinOpKind::Add, .. })
577                            | Op::AssignOp(Spanned { node: hir::AssignOpKind::AddAssign, .. })
578                    ) && self
579                        .check_str_addition(lhs_expr, rhs_expr, lhs_ty, rhs_ty, &mut err, op)
580                    {
581                        // This has nothing here because it means we did string
582                        // concatenation (e.g., "Hello " + "World!"). This means
583                        // we don't want the note in the else clause to be emitted
584                    } else if lhs_ty.has_non_region_param() {
585                        // Look for a TraitPredicate in the Fulfillment errors,
586                        // and use it to generate a suggestion.
587                        //
588                        // Note that lookup_op_method must be called again but
589                        // with a specific rhs_ty instead of a placeholder so
590                        // the resulting predicate generates a more specific
591                        // suggestion for the user.
592                        let errors = self
593                            .lookup_op_method(
594                                (lhs_expr, lhs_ty),
595                                Some((rhs_expr, rhs_ty)),
596                                lang_item_for_binop(self.tcx, op),
597                                op.span(),
598                                expected,
599                            )
600                            .unwrap_err();
601                        if !errors.is_empty() {
602                            for error in errors {
603                                if let Some(trait_pred) =
604                                    error.obligation.predicate.as_trait_clause()
605                                {
606                                    let output_associated_item = match error.obligation.cause.code()
607                                    {
608                                        ObligationCauseCode::BinOp {
609                                            output_ty: Some(output_ty),
610                                            ..
611                                        } => {
612                                            // Make sure that we're attaching `Output = ..` to the right trait predicate
613                                            if let Some(output_def_id) = output_def_id
614                                                && let Some(trait_def_id) = trait_def_id
615                                                && self.tcx.parent(output_def_id) == trait_def_id
616                                                && let Some(output_ty) = output_ty
617                                                    .make_suggestable(self.tcx, false, None)
618                                            {
619                                                Some(("Output", output_ty))
620                                            } else {
621                                                None
622                                            }
623                                        }
624                                        _ => None,
625                                    };
626
627                                    self.err_ctxt().suggest_restricting_param_bound(
628                                        &mut err,
629                                        trait_pred,
630                                        output_associated_item,
631                                        self.body_id,
632                                    );
633                                }
634                            }
635                        } else {
636                            // When we know that a missing bound is responsible, we don't show
637                            // this note as it is redundant.
638                            err.note(format!(
639                                "the trait `{missing_trait}` is not implemented for `{lhs_ty_str}`"
640                            ));
641                        }
642                    }
643                }
644
645                // Suggest using `add`, `offset` or `offset_from` for pointer - {integer},
646                // pointer + {integer} or pointer - pointer.
647                if op.span().can_be_used_for_suggestions() {
648                    match op {
649                        Op::BinOp(Spanned { node: hir::BinOpKind::Add, .. })
650                            if lhs_ty.is_raw_ptr() && rhs_ty.is_integral() =>
651                        {
652                            err.multipart_suggestion(
653                                "consider using `wrapping_add` or `add` for pointer + {integer}",
654                                vec![
655                                    (
656                                        lhs_expr.span.between(rhs_expr.span),
657                                        ".wrapping_add(".to_owned(),
658                                    ),
659                                    (rhs_expr.span.shrink_to_hi(), ")".to_owned()),
660                                ],
661                                Applicability::MaybeIncorrect,
662                            );
663                        }
664                        Op::BinOp(Spanned { node: hir::BinOpKind::Sub, .. }) => {
665                            if lhs_ty.is_raw_ptr() && rhs_ty.is_integral() {
666                                err.multipart_suggestion(
667                                    "consider using `wrapping_sub` or `sub` for \
668                                     pointer - {integer}",
669                                    vec![
670                                        (
671                                            lhs_expr.span.between(rhs_expr.span),
672                                            ".wrapping_sub(".to_owned(),
673                                        ),
674                                        (rhs_expr.span.shrink_to_hi(), ")".to_owned()),
675                                    ],
676                                    Applicability::MaybeIncorrect,
677                                );
678                            }
679
680                            if lhs_ty.is_raw_ptr() && rhs_ty.is_raw_ptr() {
681                                err.multipart_suggestion(
682                                    "consider using `offset_from` for pointer - pointer if the \
683                                     pointers point to the same allocation",
684                                    vec![
685                                        (lhs_expr.span.shrink_to_lo(), "unsafe { ".to_owned()),
686                                        (
687                                            lhs_expr.span.between(rhs_expr.span),
688                                            ".offset_from(".to_owned(),
689                                        ),
690                                        (rhs_expr.span.shrink_to_hi(), ") }".to_owned()),
691                                    ],
692                                    Applicability::MaybeIncorrect,
693                                );
694                            }
695                        }
696                        _ => {}
697                    }
698                }
699
700                let lhs_name_str = match lhs_expr.kind {
701                    hir::ExprKind::Path(hir::QPath::Resolved(_, path)) => {
702                        path.segments.last().map_or("_".to_string(), |s| s.ident.to_string())
703                    }
704                    _ => self
705                        .tcx
706                        .sess
707                        .source_map()
708                        .span_to_snippet(lhs_expr.span)
709                        .unwrap_or("_".to_string()),
710                };
711
712                if op.span().can_be_used_for_suggestions() {
713                    match op {
714                        Op::AssignOp(Spanned { node: hir::AssignOpKind::AddAssign, .. })
715                            if lhs_ty.is_raw_ptr() && rhs_ty.is_integral() =>
716                        {
717                            err.multipart_suggestion(
718                                "consider using `add` or `wrapping_add` to do pointer arithmetic",
719                                vec![
720                                    (lhs_expr.span.shrink_to_lo(), format!("{} = ", lhs_name_str)),
721                                    (
722                                        lhs_expr.span.between(rhs_expr.span),
723                                        ".wrapping_add(".to_owned(),
724                                    ),
725                                    (rhs_expr.span.shrink_to_hi(), ")".to_owned()),
726                                ],
727                                Applicability::MaybeIncorrect,
728                            );
729                        }
730                        Op::AssignOp(Spanned { node: hir::AssignOpKind::SubAssign, .. }) => {
731                            if lhs_ty.is_raw_ptr() && rhs_ty.is_integral() {
732                                err.multipart_suggestion(
733                                    "consider using `sub` or `wrapping_sub` to do pointer arithmetic",
734                                    vec![
735                                        (lhs_expr.span.shrink_to_lo(), format!("{} = ", lhs_name_str)),
736                                        (
737                                            lhs_expr.span.between(rhs_expr.span),
738                                            ".wrapping_sub(".to_owned(),
739
740                                        ),
741                                        (rhs_expr.span.shrink_to_hi(), ")".to_owned()),
742                                    ],
743                                    Applicability::MaybeIncorrect,
744                                );
745                            }
746                        }
747                        _ => {}
748                    }
749                }
750
751                let reported = err.emit();
752                Ty::new_error(self.tcx, reported)
753            }
754        };
755
756        (lhs_ty, rhs_ty, return_ty)
757    }
758
759    /// Provide actionable suggestions when trying to add two strings with incorrect types,
760    /// like `&str + &str`, `String + String` and `&str + &String`.
761    ///
762    /// If this function returns `true` it means a note was printed, so we don't need
763    /// to print the normal "implementation of `std::ops::Add` might be missing" note
764    fn check_str_addition(
765        &self,
766        lhs_expr: &'tcx hir::Expr<'tcx>,
767        rhs_expr: &'tcx hir::Expr<'tcx>,
768        lhs_ty: Ty<'tcx>,
769        rhs_ty: Ty<'tcx>,
770        err: &mut Diag<'_>,
771        op: Op,
772    ) -> bool {
773        let str_concat_note = "string concatenation requires an owned `String` on the left";
774        let rm_borrow_msg = "remove the borrow to obtain an owned `String`";
775        let to_owned_msg = "create an owned `String` from a string reference";
776
777        let string_type = self.tcx.lang_items().string();
778        let is_std_string =
779            |ty: Ty<'tcx>| ty.ty_adt_def().is_some_and(|ty_def| Some(ty_def.did()) == string_type);
780
781        match (lhs_ty.kind(), rhs_ty.kind()) {
782            (&ty::Ref(_, l_ty, _), &ty::Ref(_, r_ty, _)) // &str or &String + &str, &String or &&str
783                if (*l_ty.kind() == ty::Str || is_std_string(l_ty))
784                    && (*r_ty.kind() == ty::Str
785                        || is_std_string(r_ty)
786                        || matches!(
787                            r_ty.kind(), ty::Ref(_, inner_ty, _) if *inner_ty.kind() == ty::Str
788                        )) =>
789            {
790                if let Op::BinOp(_) = op { // Do not supply this message if `&str += &str`
791                    err.span_label(
792                        op.span(),
793                        "`+` cannot be used to concatenate two `&str` strings"
794                    );
795                    err.note(str_concat_note);
796                    if let hir::ExprKind::AddrOf(_, _, lhs_inner_expr) = lhs_expr.kind {
797                        err.span_suggestion_verbose(
798                            lhs_expr.span.until(lhs_inner_expr.span),
799                            rm_borrow_msg,
800                            "",
801                            Applicability::MachineApplicable
802                        );
803                    } else {
804                        err.span_suggestion_verbose(
805                            lhs_expr.span.shrink_to_hi(),
806                            to_owned_msg,
807                            ".to_owned()",
808                            Applicability::MachineApplicable
809                        );
810                    }
811                }
812                true
813            }
814            (&ty::Ref(_, l_ty, _), &ty::Adt(..)) // Handle `&str` & `&String` + `String`
815                if (*l_ty.kind() == ty::Str || is_std_string(l_ty)) && is_std_string(rhs_ty) =>
816            {
817                err.span_label(
818                    op.span(),
819                    "`+` cannot be used to concatenate a `&str` with a `String`",
820                );
821                match op {
822                    Op::BinOp(_) => {
823                        let sugg_msg;
824                        let lhs_sugg = if let hir::ExprKind::AddrOf(_, _, lhs_inner_expr) = lhs_expr.kind {
825                            sugg_msg = "remove the borrow on the left and add one on the right";
826                            (lhs_expr.span.until(lhs_inner_expr.span), "".to_owned())
827                        } else {
828                            sugg_msg = "create an owned `String` on the left and add a borrow on the right";
829                            (lhs_expr.span.shrink_to_hi(), ".to_owned()".to_owned())
830                        };
831                        let suggestions = vec![
832                            lhs_sugg,
833                            (rhs_expr.span.shrink_to_lo(), "&".to_owned()),
834                        ];
835                        err.multipart_suggestion_verbose(
836                            sugg_msg,
837                            suggestions,
838                            Applicability::MachineApplicable,
839                        );
840                    }
841                    Op::AssignOp(_) => {
842                        err.note(str_concat_note);
843                    }
844                }
845                true
846            }
847            _ => false,
848        }
849    }
850
851    pub(crate) fn check_user_unop(
852        &self,
853        ex: &'tcx hir::Expr<'tcx>,
854        operand_ty: Ty<'tcx>,
855        op: hir::UnOp,
856        expected: Expectation<'tcx>,
857    ) -> Ty<'tcx> {
858        assert!(op.is_by_value());
859        match self.lookup_op_method(
860            (ex, operand_ty),
861            None,
862            lang_item_for_unop(self.tcx, op),
863            ex.span,
864            expected,
865        ) {
866            Ok(method) => {
867                self.write_method_call_and_enforce_effects(ex.hir_id, ex.span, method);
868                method.sig.output()
869            }
870            Err(errors) => {
871                let actual = self.resolve_vars_if_possible(operand_ty);
872                let guar = actual.error_reported().err().unwrap_or_else(|| {
873                    let mut file = None;
874                    let ty_str = self.tcx.short_string(actual, &mut file);
875                    let mut err = struct_span_code_err!(
876                        self.dcx(),
877                        ex.span,
878                        E0600,
879                        "cannot apply unary operator `{}` to type `{ty_str}`",
880                        op.as_str(),
881                    );
882                    *err.long_ty_path() = file;
883                    err.span_label(
884                        ex.span,
885                        format!("cannot apply unary operator `{}`", op.as_str()),
886                    );
887
888                    if operand_ty.has_non_region_param() {
889                        let predicates = errors
890                            .iter()
891                            .filter_map(|error| error.obligation.predicate.as_trait_clause());
892                        for pred in predicates {
893                            self.err_ctxt().suggest_restricting_param_bound(
894                                &mut err,
895                                pred,
896                                None,
897                                self.body_id,
898                            );
899                        }
900                    }
901
902                    let sp = self.tcx.sess.source_map().start_point(ex.span).with_parent(None);
903                    if let Some(sp) =
904                        self.tcx.sess.psess.ambiguous_block_expr_parse.borrow().get(&sp)
905                    {
906                        // If the previous expression was a block expression, suggest parentheses
907                        // (turning this into a binary subtraction operation instead.)
908                        // for example, `{2} - 2` -> `({2}) - 2` (see src\test\ui\parser\expr-as-stmt.rs)
909                        err.subdiagnostic(ExprParenthesesNeeded::surrounding(*sp));
910                    } else {
911                        match actual.kind() {
912                            ty::Uint(_) if op == hir::UnOp::Neg => {
913                                err.note("unsigned values cannot be negated");
914
915                                if let hir::ExprKind::Unary(
916                                    _,
917                                    hir::Expr {
918                                        kind:
919                                            hir::ExprKind::Lit(Spanned {
920                                                node: ast::LitKind::Int(Pu128(1), _),
921                                                ..
922                                            }),
923                                        ..
924                                    },
925                                ) = ex.kind
926                                {
927                                    let span = if let hir::Node::Expr(parent) =
928                                        self.tcx.parent_hir_node(ex.hir_id)
929                                        && let hir::ExprKind::Cast(..) = parent.kind
930                                    {
931                                        // `-1 as usize` -> `usize::MAX`
932                                        parent.span
933                                    } else {
934                                        ex.span
935                                    };
936                                    err.span_suggestion_verbose(
937                                        span,
938                                        format!(
939                                            "you may have meant the maximum value of `{actual}`",
940                                        ),
941                                        format!("{actual}::MAX"),
942                                        Applicability::MaybeIncorrect,
943                                    );
944                                }
945                            }
946                            ty::Str | ty::Never | ty::Char | ty::Tuple(_) | ty::Array(_, _) => {}
947                            ty::Ref(_, lty, _) if *lty.kind() == ty::Str => {}
948                            _ => {
949                                self.note_unmet_impls_on_type(&mut err, errors, true);
950                            }
951                        }
952                    }
953                    err.emit()
954                });
955                Ty::new_error(self.tcx, guar)
956            }
957        }
958    }
959
960    fn lookup_op_method(
961        &self,
962        (lhs_expr, lhs_ty): (&'tcx hir::Expr<'tcx>, Ty<'tcx>),
963        opt_rhs: Option<(&'tcx hir::Expr<'tcx>, Ty<'tcx>)>,
964        (opname, trait_did): (Symbol, Option<hir::def_id::DefId>),
965        span: Span,
966        expected: Expectation<'tcx>,
967    ) -> Result<MethodCallee<'tcx>, Vec<FulfillmentError<'tcx>>> {
968        let Some(trait_did) = trait_did else {
969            // Bail if the operator trait is not defined.
970            return Err(vec![]);
971        };
972
973        debug!(
974            "lookup_op_method(lhs_ty={:?}, opname={:?}, trait_did={:?})",
975            lhs_ty, opname, trait_did
976        );
977
978        let (opt_rhs_expr, opt_rhs_ty) = opt_rhs.unzip();
979        let cause = self.cause(
980            span,
981            ObligationCauseCode::BinOp {
982                lhs_hir_id: lhs_expr.hir_id,
983                rhs_hir_id: opt_rhs_expr.map(|expr| expr.hir_id),
984                rhs_span: opt_rhs_expr.map(|expr| expr.span),
985                rhs_is_lit: opt_rhs_expr
986                    .is_some_and(|expr| matches!(expr.kind, hir::ExprKind::Lit(_))),
987                output_ty: expected.only_has_type(self),
988            },
989        );
990
991        let method =
992            self.lookup_method_for_operator(cause.clone(), opname, trait_did, lhs_ty, opt_rhs_ty);
993        match method {
994            Some(ok) => {
995                let method = self.register_infer_ok_obligations(ok);
996                self.select_obligations_where_possible(|_| {});
997                Ok(method)
998            }
999            None => {
1000                // This path may do some inference, so make sure we've really
1001                // doomed compilation so as to not accidentally stabilize new
1002                // inference or something here...
1003                self.dcx().span_delayed_bug(span, "this path really should be doomed...");
1004                // Guide inference for the RHS expression if it's provided --
1005                // this will allow us to better error reporting, at the expense
1006                // of making some error messages a bit more specific.
1007                if let Some((rhs_expr, rhs_ty)) = opt_rhs
1008                    && rhs_ty.is_ty_var()
1009                {
1010                    self.check_expr_coercible_to_type(rhs_expr, rhs_ty, None);
1011                }
1012
1013                // Construct an obligation `self_ty : Trait<input_tys>`
1014                let args =
1015                    ty::GenericArgs::for_item(self.tcx, trait_did, |param, _| match param.kind {
1016                        ty::GenericParamDefKind::Lifetime
1017                        | ty::GenericParamDefKind::Const { .. } => {
1018                            unreachable!("did not expect operand trait to have lifetime/const args")
1019                        }
1020                        ty::GenericParamDefKind::Type { .. } => {
1021                            if param.index == 0 {
1022                                lhs_ty.into()
1023                            } else {
1024                                opt_rhs_ty.expect("expected RHS for binop").into()
1025                            }
1026                        }
1027                    });
1028                let obligation = Obligation::new(
1029                    self.tcx,
1030                    cause,
1031                    self.param_env,
1032                    ty::TraitRef::new_from_args(self.tcx, trait_did, args),
1033                );
1034                let ocx = ObligationCtxt::new_with_diagnostics(&self.infcx);
1035                ocx.register_obligation(obligation);
1036                Err(ocx.select_all_or_error())
1037            }
1038        }
1039    }
1040}
1041
1042fn lang_item_for_binop(tcx: TyCtxt<'_>, op: Op) -> (Symbol, Option<hir::def_id::DefId>) {
1043    let lang = tcx.lang_items();
1044    match op {
1045        Op::AssignOp(op) => match op.node {
1046            hir::AssignOpKind::AddAssign => (sym::add_assign, lang.add_assign_trait()),
1047            hir::AssignOpKind::SubAssign => (sym::sub_assign, lang.sub_assign_trait()),
1048            hir::AssignOpKind::MulAssign => (sym::mul_assign, lang.mul_assign_trait()),
1049            hir::AssignOpKind::DivAssign => (sym::div_assign, lang.div_assign_trait()),
1050            hir::AssignOpKind::RemAssign => (sym::rem_assign, lang.rem_assign_trait()),
1051            hir::AssignOpKind::BitXorAssign => (sym::bitxor_assign, lang.bitxor_assign_trait()),
1052            hir::AssignOpKind::BitAndAssign => (sym::bitand_assign, lang.bitand_assign_trait()),
1053            hir::AssignOpKind::BitOrAssign => (sym::bitor_assign, lang.bitor_assign_trait()),
1054            hir::AssignOpKind::ShlAssign => (sym::shl_assign, lang.shl_assign_trait()),
1055            hir::AssignOpKind::ShrAssign => (sym::shr_assign, lang.shr_assign_trait()),
1056        },
1057        Op::BinOp(op) => match op.node {
1058            hir::BinOpKind::Add => (sym::add, lang.add_trait()),
1059            hir::BinOpKind::Sub => (sym::sub, lang.sub_trait()),
1060            hir::BinOpKind::Mul => (sym::mul, lang.mul_trait()),
1061            hir::BinOpKind::Div => (sym::div, lang.div_trait()),
1062            hir::BinOpKind::Rem => (sym::rem, lang.rem_trait()),
1063            hir::BinOpKind::BitXor => (sym::bitxor, lang.bitxor_trait()),
1064            hir::BinOpKind::BitAnd => (sym::bitand, lang.bitand_trait()),
1065            hir::BinOpKind::BitOr => (sym::bitor, lang.bitor_trait()),
1066            hir::BinOpKind::Shl => (sym::shl, lang.shl_trait()),
1067            hir::BinOpKind::Shr => (sym::shr, lang.shr_trait()),
1068            hir::BinOpKind::Lt => (sym::lt, lang.partial_ord_trait()),
1069            hir::BinOpKind::Le => (sym::le, lang.partial_ord_trait()),
1070            hir::BinOpKind::Ge => (sym::ge, lang.partial_ord_trait()),
1071            hir::BinOpKind::Gt => (sym::gt, lang.partial_ord_trait()),
1072            hir::BinOpKind::Eq => (sym::eq, lang.eq_trait()),
1073            hir::BinOpKind::Ne => (sym::ne, lang.eq_trait()),
1074            hir::BinOpKind::And | hir::BinOpKind::Or => {
1075                bug!("&& and || are not overloadable")
1076            }
1077        },
1078    }
1079}
1080
1081fn lang_item_for_unop(tcx: TyCtxt<'_>, op: hir::UnOp) -> (Symbol, Option<hir::def_id::DefId>) {
1082    let lang = tcx.lang_items();
1083    match op {
1084        hir::UnOp::Not => (sym::not, lang.not_trait()),
1085        hir::UnOp::Neg => (sym::neg, lang.neg_trait()),
1086        hir::UnOp::Deref => bug!("Deref is not overloadable"),
1087    }
1088}
1089
1090// Binary operator categories. These categories summarize the behavior
1091// with respect to the builtin operations supported.
1092#[derive(Clone, Copy)]
1093enum BinOpCategory {
1094    /// &&, || -- cannot be overridden
1095    Shortcircuit,
1096
1097    /// <<, >> -- when shifting a single integer, rhs can be any
1098    /// integer type. For simd, types must match.
1099    Shift,
1100
1101    /// +, -, etc -- takes equal types, produces same type as input,
1102    /// applicable to ints/floats/simd
1103    Math,
1104
1105    /// &, |, ^ -- takes equal types, produces same type as input,
1106    /// applicable to ints/floats/simd/bool
1107    Bitwise,
1108
1109    /// ==, !=, etc -- takes equal types, produces bools, except for simd,
1110    /// which produce the input type
1111    Comparison,
1112}
1113
1114impl From<hir::BinOpKind> for BinOpCategory {
1115    fn from(op: hir::BinOpKind) -> BinOpCategory {
1116        use hir::BinOpKind::*;
1117        match op {
1118            Shl | Shr => BinOpCategory::Shift,
1119            Add | Sub | Mul | Div | Rem => BinOpCategory::Math,
1120            BitXor | BitAnd | BitOr => BinOpCategory::Bitwise,
1121            Eq | Ne | Lt | Le | Ge | Gt => BinOpCategory::Comparison,
1122            And | Or => BinOpCategory::Shortcircuit,
1123        }
1124    }
1125}
1126
1127impl From<hir::AssignOpKind> for BinOpCategory {
1128    fn from(op: hir::AssignOpKind) -> BinOpCategory {
1129        use hir::AssignOpKind::*;
1130        match op {
1131            ShlAssign | ShrAssign => BinOpCategory::Shift,
1132            AddAssign | SubAssign | MulAssign | DivAssign | RemAssign => BinOpCategory::Math,
1133            BitXorAssign | BitAndAssign | BitOrAssign => BinOpCategory::Bitwise,
1134        }
1135    }
1136}
1137
1138/// An assignment op (e.g. `a += b`), or a binary op (e.g. `a + b`).
1139#[derive(Clone, Copy, Debug, PartialEq)]
1140enum Op {
1141    BinOp(hir::BinOp),
1142    AssignOp(hir::AssignOp),
1143}
1144
1145impl Op {
1146    fn span(&self) -> Span {
1147        match self {
1148            Op::BinOp(op) => op.span,
1149            Op::AssignOp(op) => op.span,
1150        }
1151    }
1152
1153    fn as_str(&self) -> &'static str {
1154        match self {
1155            Op::BinOp(op) => op.node.as_str(),
1156            Op::AssignOp(op) => op.node.as_str(),
1157        }
1158    }
1159
1160    fn is_by_value(&self) -> bool {
1161        match self {
1162            Op::BinOp(op) => op.node.is_by_value(),
1163            Op::AssignOp(op) => op.node.is_by_value(),
1164        }
1165    }
1166}
1167
1168/// Dereferences a single level of immutable referencing.
1169fn deref_ty_if_possible(ty: Ty<'_>) -> Ty<'_> {
1170    match ty.kind() {
1171        ty::Ref(_, ty, hir::Mutability::Not) => *ty,
1172        _ => ty,
1173    }
1174}
1175
1176/// Returns `true` if this is a built-in arithmetic operation (e.g., u32
1177/// + u32, i16x4 == i16x4) and false if these types would have to be
1178/// overloaded to be legal. There are two reasons that we distinguish
1179/// builtin operations from overloaded ones (vs trying to drive
1180/// everything uniformly through the trait system and intrinsics or
1181/// something like that):
1182///
1183/// 1. Builtin operations can trivially be evaluated in constants.
1184/// 2. For comparison operators applied to SIMD types the result is
1185///    not of type `bool`. For example, `i16x4 == i16x4` yields a
1186///    type like `i16x4`. This means that the overloaded trait
1187///    `PartialEq` is not applicable.
1188///
1189/// Reason #2 is the killer. I tried for a while to always use
1190/// overloaded logic and just check the types in constants/codegen after
1191/// the fact, and it worked fine, except for SIMD types. -nmatsakis
1192fn is_builtin_binop<'tcx>(lhs: Ty<'tcx>, rhs: Ty<'tcx>, category: BinOpCategory) -> bool {
1193    // Special-case a single layer of referencing, so that things like `5.0 + &6.0f32` work.
1194    // (See https://github.com/rust-lang/rust/issues/57447.)
1195    let (lhs, rhs) = (deref_ty_if_possible(lhs), deref_ty_if_possible(rhs));
1196
1197    match category.into() {
1198        BinOpCategory::Shortcircuit => true,
1199        BinOpCategory::Shift => {
1200            lhs.references_error()
1201                || rhs.references_error()
1202                || lhs.is_integral() && rhs.is_integral()
1203        }
1204        BinOpCategory::Math => {
1205            lhs.references_error()
1206                || rhs.references_error()
1207                || lhs.is_integral() && rhs.is_integral()
1208                || lhs.is_floating_point() && rhs.is_floating_point()
1209        }
1210        BinOpCategory::Bitwise => {
1211            lhs.references_error()
1212                || rhs.references_error()
1213                || lhs.is_integral() && rhs.is_integral()
1214                || lhs.is_floating_point() && rhs.is_floating_point()
1215                || lhs.is_bool() && rhs.is_bool()
1216        }
1217        BinOpCategory::Comparison => {
1218            lhs.references_error() || rhs.references_error() || lhs.is_scalar() && rhs.is_scalar()
1219        }
1220    }
1221}