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                        if !errors.is_empty() {
586                            for error in errors {
587                                if let Some(trait_pred) =
588                                    error.obligation.predicate.as_trait_clause()
589                                {
590                                    let output_associated_item = match error.obligation.cause.code()
591                                    {
592                                        ObligationCauseCode::BinOp {
593                                            output_ty: Some(output_ty),
594                                            ..
595                                        } => {
596                                            // Make sure that we're attaching `Output = ..` to the right trait predicate
597                                            if let Some(output_def_id) = output_def_id
598                                                && let Some(trait_def_id) = trait_def_id
599                                                && self.tcx.parent(output_def_id) == trait_def_id
600                                                && let Some(output_ty) = output_ty
601                                                    .make_suggestable(self.tcx, false, None)
602                                            {
603                                                Some(("Output", output_ty))
604                                            } else {
605                                                None
606                                            }
607                                        }
608                                        _ => None,
609                                    };
610
611                                    self.err_ctxt().suggest_restricting_param_bound(
612                                        &mut err,
613                                        trait_pred,
614                                        output_associated_item,
615                                        self.body_id,
616                                    );
617                                }
618                            }
619                        } else {
620                            // When we know that a missing bound is responsible, we don't show
621                            // this note as it is redundant.
622                            err.note(format!(
623                                "the trait `{missing_trait}` is not implemented for `{lhs_ty_str}`"
624                            ));
625                        }
626                    }
627                }
628
629                // Suggest using `add`, `offset` or `offset_from` for pointer - {integer},
630                // pointer + {integer} or pointer - pointer.
631                if op.span().can_be_used_for_suggestions() {
632                    match op {
633                        Op::BinOp(Spanned { node: hir::BinOpKind::Add, .. })
634                            if lhs_ty.is_raw_ptr() && rhs_ty.is_integral() =>
635                        {
636                            err.multipart_suggestion(
637                                "consider using `wrapping_add` or `add` for pointer + {integer}",
638                                vec![
639                                    (
640                                        lhs_expr.span.between(rhs_expr.span),
641                                        ".wrapping_add(".to_owned(),
642                                    ),
643                                    (rhs_expr.span.shrink_to_hi(), ")".to_owned()),
644                                ],
645                                Applicability::MaybeIncorrect,
646                            );
647                        }
648                        Op::BinOp(Spanned { node: hir::BinOpKind::Sub, .. }) => {
649                            if lhs_ty.is_raw_ptr() && rhs_ty.is_integral() {
650                                err.multipart_suggestion(
651                                    "consider using `wrapping_sub` or `sub` for \
652                                     pointer - {integer}",
653                                    vec![
654                                        (
655                                            lhs_expr.span.between(rhs_expr.span),
656                                            ".wrapping_sub(".to_owned(),
657                                        ),
658                                        (rhs_expr.span.shrink_to_hi(), ")".to_owned()),
659                                    ],
660                                    Applicability::MaybeIncorrect,
661                                );
662                            }
663
664                            if lhs_ty.is_raw_ptr() && rhs_ty.is_raw_ptr() {
665                                err.multipart_suggestion(
666                                    "consider using `offset_from` for pointer - pointer if the \
667                                     pointers point to the same allocation",
668                                    vec![
669                                        (lhs_expr.span.shrink_to_lo(), "unsafe { ".to_owned()),
670                                        (
671                                            lhs_expr.span.between(rhs_expr.span),
672                                            ".offset_from(".to_owned(),
673                                        ),
674                                        (rhs_expr.span.shrink_to_hi(), ") }".to_owned()),
675                                    ],
676                                    Applicability::MaybeIncorrect,
677                                );
678                            }
679                        }
680                        _ => {}
681                    }
682                }
683
684                let lhs_name_str = match lhs_expr.kind {
685                    hir::ExprKind::Path(hir::QPath::Resolved(_, path)) => {
686                        path.segments.last().map_or("_".to_string(), |s| s.ident.to_string())
687                    }
688                    _ => self
689                        .tcx
690                        .sess
691                        .source_map()
692                        .span_to_snippet(lhs_expr.span)
693                        .unwrap_or_else(|_| "_".to_string()),
694                };
695
696                if op.span().can_be_used_for_suggestions() {
697                    match op {
698                        Op::AssignOp(Spanned { node: hir::AssignOpKind::AddAssign, .. })
699                            if lhs_ty.is_raw_ptr() && rhs_ty.is_integral() =>
700                        {
701                            err.multipart_suggestion(
702                                "consider using `add` or `wrapping_add` to do pointer arithmetic",
703                                vec![
704                                    (lhs_expr.span.shrink_to_lo(), format!("{} = ", lhs_name_str)),
705                                    (
706                                        lhs_expr.span.between(rhs_expr.span),
707                                        ".wrapping_add(".to_owned(),
708                                    ),
709                                    (rhs_expr.span.shrink_to_hi(), ")".to_owned()),
710                                ],
711                                Applicability::MaybeIncorrect,
712                            );
713                        }
714                        Op::AssignOp(Spanned { node: hir::AssignOpKind::SubAssign, .. }) => {
715                            if lhs_ty.is_raw_ptr() && rhs_ty.is_integral() {
716                                err.multipart_suggestion(
717                                    "consider using `sub` or `wrapping_sub` to do pointer arithmetic",
718                                    vec![
719                                        (lhs_expr.span.shrink_to_lo(), format!("{} = ", lhs_name_str)),
720                                        (
721                                            lhs_expr.span.between(rhs_expr.span),
722                                            ".wrapping_sub(".to_owned(),
723
724                                        ),
725                                        (rhs_expr.span.shrink_to_hi(), ")".to_owned()),
726                                    ],
727                                    Applicability::MaybeIncorrect,
728                                );
729                            }
730                        }
731                        _ => {}
732                    }
733                }
734
735                let reported = err.emit();
736                Ty::new_error(self.tcx, reported)
737            }
738        };
739
740        (lhs_ty, rhs_ty, return_ty)
741    }
742
743    /// Provide actionable suggestions when trying to add two strings with incorrect types,
744    /// like `&str + &str`, `String + String` and `&str + &String`.
745    ///
746    /// If this function returns `true` it means a note was printed, so we don't need
747    /// to print the normal "implementation of `std::ops::Add` might be missing" note
748    fn check_str_addition(
749        &self,
750        lhs_expr: &'tcx hir::Expr<'tcx>,
751        rhs_expr: &'tcx hir::Expr<'tcx>,
752        lhs_ty: Ty<'tcx>,
753        rhs_ty: Ty<'tcx>,
754        err: &mut Diag<'_>,
755        op: Op,
756    ) -> bool {
757        let str_concat_note = "string concatenation requires an owned `String` on the left";
758        let rm_borrow_msg = "remove the borrow to obtain an owned `String`";
759        let to_owned_msg = "create an owned `String` from a string reference";
760
761        let string_type = self.tcx.lang_items().string();
762        let is_std_string =
763            |ty: Ty<'tcx>| ty.ty_adt_def().is_some_and(|ty_def| Some(ty_def.did()) == string_type);
764
765        match (lhs_ty.kind(), rhs_ty.kind()) {
766            (&ty::Ref(_, l_ty, _), &ty::Ref(_, r_ty, _)) // &str or &String + &str, &String or &&str
767                if (*l_ty.kind() == ty::Str || is_std_string(l_ty))
768                    && (*r_ty.kind() == ty::Str
769                        || is_std_string(r_ty)
770                        || matches!(
771                            r_ty.kind(), ty::Ref(_, inner_ty, _) if *inner_ty.kind() == ty::Str
772                        )) =>
773            {
774                if let Op::BinOp(_) = op { // Do not supply this message if `&str += &str`
775                    err.span_label(
776                        op.span(),
777                        "`+` cannot be used to concatenate two `&str` strings"
778                    );
779                    err.note(str_concat_note);
780                    if let hir::ExprKind::AddrOf(_, _, lhs_inner_expr) = lhs_expr.kind {
781                        err.span_suggestion_verbose(
782                            lhs_expr.span.until(lhs_inner_expr.span),
783                            rm_borrow_msg,
784                            "",
785                            Applicability::MachineApplicable
786                        );
787                    } else {
788                        err.span_suggestion_verbose(
789                            lhs_expr.span.shrink_to_hi(),
790                            to_owned_msg,
791                            ".to_owned()",
792                            Applicability::MachineApplicable
793                        );
794                    }
795                }
796                true
797            }
798            (&ty::Ref(_, l_ty, _), &ty::Adt(..)) // Handle `&str` & `&String` + `String`
799                if (*l_ty.kind() == ty::Str || is_std_string(l_ty)) && is_std_string(rhs_ty) =>
800            {
801                err.span_label(
802                    op.span(),
803                    "`+` cannot be used to concatenate a `&str` with a `String`",
804                );
805                match op {
806                    Op::BinOp(_) => {
807                        let sugg_msg;
808                        let lhs_sugg = if let hir::ExprKind::AddrOf(_, _, lhs_inner_expr) = lhs_expr.kind {
809                            sugg_msg = "remove the borrow on the left and add one on the right";
810                            (lhs_expr.span.until(lhs_inner_expr.span), "".to_owned())
811                        } else {
812                            sugg_msg = "create an owned `String` on the left and add a borrow on the right";
813                            (lhs_expr.span.shrink_to_hi(), ".to_owned()".to_owned())
814                        };
815                        let suggestions = vec![
816                            lhs_sugg,
817                            (rhs_expr.span.shrink_to_lo(), "&".to_owned()),
818                        ];
819                        err.multipart_suggestion_verbose(
820                            sugg_msg,
821                            suggestions,
822                            Applicability::MachineApplicable,
823                        );
824                    }
825                    Op::AssignOp(_) => {
826                        err.note(str_concat_note);
827                    }
828                }
829                true
830            }
831            _ => false,
832        }
833    }
834
835    pub(crate) fn check_user_unop(
836        &self,
837        ex: &'tcx hir::Expr<'tcx>,
838        operand_ty: Ty<'tcx>,
839        op: hir::UnOp,
840        expected: Expectation<'tcx>,
841    ) -> Ty<'tcx> {
842        assert!(op.is_by_value());
843        match self.lookup_op_method(
844            (ex, operand_ty),
845            None,
846            lang_item_for_unop(self.tcx, op),
847            ex.span,
848            expected,
849        ) {
850            Ok(method) => {
851                self.write_method_call_and_enforce_effects(ex.hir_id, ex.span, method);
852                method.sig.output()
853            }
854            Err(errors) => {
855                let actual = self.resolve_vars_if_possible(operand_ty);
856                let guar = actual.error_reported().err().unwrap_or_else(|| {
857                    let mut file = None;
858                    let ty_str = self.tcx.short_string(actual, &mut file);
859                    let mut err = struct_span_code_err!(
860                        self.dcx(),
861                        ex.span,
862                        E0600,
863                        "cannot apply unary operator `{}` to type `{ty_str}`",
864                        op.as_str(),
865                    );
866                    *err.long_ty_path() = file;
867                    err.span_label(
868                        ex.span,
869                        format!("cannot apply unary operator `{}`", op.as_str()),
870                    );
871
872                    if operand_ty.has_non_region_param() {
873                        let predicates = errors
874                            .iter()
875                            .filter_map(|error| error.obligation.predicate.as_trait_clause());
876                        for pred in predicates {
877                            self.err_ctxt().suggest_restricting_param_bound(
878                                &mut err,
879                                pred,
880                                None,
881                                self.body_id,
882                            );
883                        }
884                    }
885
886                    let sp = self.tcx.sess.source_map().start_point(ex.span).with_parent(None);
887                    if let Some(sp) =
888                        self.tcx.sess.psess.ambiguous_block_expr_parse.borrow().get(&sp)
889                    {
890                        // If the previous expression was a block expression, suggest parentheses
891                        // (turning this into a binary subtraction operation instead.)
892                        // for example, `{2} - 2` -> `({2}) - 2` (see src\test\ui\parser\expr-as-stmt.rs)
893                        err.subdiagnostic(ExprParenthesesNeeded::surrounding(*sp));
894                    } else {
895                        match actual.kind() {
896                            ty::Uint(_) if op == hir::UnOp::Neg => {
897                                err.note("unsigned values cannot be negated");
898
899                                if let hir::ExprKind::Unary(
900                                    _,
901                                    hir::Expr {
902                                        kind:
903                                            hir::ExprKind::Lit(Spanned {
904                                                node: ast::LitKind::Int(Pu128(1), _),
905                                                ..
906                                            }),
907                                        ..
908                                    },
909                                ) = ex.kind
910                                {
911                                    let span = if let hir::Node::Expr(parent) =
912                                        self.tcx.parent_hir_node(ex.hir_id)
913                                        && let hir::ExprKind::Cast(..) = parent.kind
914                                    {
915                                        // `-1 as usize` -> `usize::MAX`
916                                        parent.span
917                                    } else {
918                                        ex.span
919                                    };
920                                    err.span_suggestion_verbose(
921                                        span,
922                                        format!(
923                                            "you may have meant the maximum value of `{actual}`",
924                                        ),
925                                        format!("{actual}::MAX"),
926                                        Applicability::MaybeIncorrect,
927                                    );
928                                }
929                            }
930                            ty::Str | ty::Never | ty::Char | ty::Tuple(_) | ty::Array(_, _) => {}
931                            ty::Ref(_, lty, _) if *lty.kind() == ty::Str => {}
932                            _ => {
933                                self.note_unmet_impls_on_type(&mut err, &errors, true);
934                            }
935                        }
936                    }
937                    err.emit()
938                });
939                Ty::new_error(self.tcx, guar)
940            }
941        }
942    }
943
944    fn lookup_op_method(
945        &self,
946        (lhs_expr, lhs_ty): (&'tcx hir::Expr<'tcx>, Ty<'tcx>),
947        opt_rhs: Option<(&'tcx hir::Expr<'tcx>, Ty<'tcx>)>,
948        (opname, trait_did): (Symbol, Option<hir::def_id::DefId>),
949        span: Span,
950        expected: Expectation<'tcx>,
951    ) -> Result<MethodCallee<'tcx>, Vec<FulfillmentError<'tcx>>> {
952        let Some(trait_did) = trait_did else {
953            // Bail if the operator trait is not defined.
954            return Err(vec![]);
955        };
956
957        debug!(
958            "lookup_op_method(lhs_ty={:?}, opname={:?}, trait_did={:?})",
959            lhs_ty, opname, trait_did
960        );
961
962        let (opt_rhs_expr, opt_rhs_ty) = opt_rhs.unzip();
963        let cause = self.cause(
964            span,
965            ObligationCauseCode::BinOp {
966                lhs_hir_id: lhs_expr.hir_id,
967                rhs_hir_id: opt_rhs_expr.map(|expr| expr.hir_id),
968                rhs_span: opt_rhs_expr.map(|expr| expr.span),
969                rhs_is_lit: opt_rhs_expr
970                    .is_some_and(|expr| matches!(expr.kind, hir::ExprKind::Lit(_))),
971                output_ty: expected.only_has_type(self),
972            },
973        );
974
975        let method =
976            self.lookup_method_for_operator(cause.clone(), opname, trait_did, lhs_ty, opt_rhs_ty);
977        match method {
978            Some(ok) => {
979                let method = self.register_infer_ok_obligations(ok);
980                self.select_obligations_where_possible(|_| {});
981                Ok(method)
982            }
983            None => {
984                // This path may do some inference, so make sure we've really
985                // doomed compilation so as to not accidentally stabilize new
986                // inference or something here...
987                self.dcx().span_delayed_bug(span, "this path really should be doomed...");
988                // Guide inference for the RHS expression if it's provided --
989                // this will allow us to better error reporting, at the expense
990                // of making some error messages a bit more specific.
991                if let Some((rhs_expr, rhs_ty)) = opt_rhs
992                    && rhs_ty.is_ty_var()
993                {
994                    self.check_expr_coercible_to_type(rhs_expr, rhs_ty, None);
995                }
996
997                // Construct an obligation `self_ty : Trait<input_tys>`
998                let args =
999                    ty::GenericArgs::for_item(self.tcx, trait_did, |param, _| match param.kind {
1000                        ty::GenericParamDefKind::Lifetime
1001                        | ty::GenericParamDefKind::Const { .. } => {
1002                            unreachable!("did not expect operand trait to have lifetime/const args")
1003                        }
1004                        ty::GenericParamDefKind::Type { .. } => {
1005                            if param.index == 0 {
1006                                lhs_ty.into()
1007                            } else {
1008                                opt_rhs_ty.expect("expected RHS for binop").into()
1009                            }
1010                        }
1011                    });
1012                let obligation = Obligation::new(
1013                    self.tcx,
1014                    cause,
1015                    self.param_env,
1016                    ty::TraitRef::new_from_args(self.tcx, trait_did, args),
1017                );
1018                let ocx = ObligationCtxt::new_with_diagnostics(&self.infcx);
1019                ocx.register_obligation(obligation);
1020                Err(ocx.select_all_or_error())
1021            }
1022        }
1023    }
1024}
1025
1026fn lang_item_for_binop(tcx: TyCtxt<'_>, op: Op) -> (Symbol, Option<hir::def_id::DefId>) {
1027    let lang = tcx.lang_items();
1028    match op {
1029        Op::AssignOp(op) => match op.node {
1030            hir::AssignOpKind::AddAssign => (sym::add_assign, lang.add_assign_trait()),
1031            hir::AssignOpKind::SubAssign => (sym::sub_assign, lang.sub_assign_trait()),
1032            hir::AssignOpKind::MulAssign => (sym::mul_assign, lang.mul_assign_trait()),
1033            hir::AssignOpKind::DivAssign => (sym::div_assign, lang.div_assign_trait()),
1034            hir::AssignOpKind::RemAssign => (sym::rem_assign, lang.rem_assign_trait()),
1035            hir::AssignOpKind::BitXorAssign => (sym::bitxor_assign, lang.bitxor_assign_trait()),
1036            hir::AssignOpKind::BitAndAssign => (sym::bitand_assign, lang.bitand_assign_trait()),
1037            hir::AssignOpKind::BitOrAssign => (sym::bitor_assign, lang.bitor_assign_trait()),
1038            hir::AssignOpKind::ShlAssign => (sym::shl_assign, lang.shl_assign_trait()),
1039            hir::AssignOpKind::ShrAssign => (sym::shr_assign, lang.shr_assign_trait()),
1040        },
1041        Op::BinOp(op) => match op.node {
1042            hir::BinOpKind::Add => (sym::add, lang.add_trait()),
1043            hir::BinOpKind::Sub => (sym::sub, lang.sub_trait()),
1044            hir::BinOpKind::Mul => (sym::mul, lang.mul_trait()),
1045            hir::BinOpKind::Div => (sym::div, lang.div_trait()),
1046            hir::BinOpKind::Rem => (sym::rem, lang.rem_trait()),
1047            hir::BinOpKind::BitXor => (sym::bitxor, lang.bitxor_trait()),
1048            hir::BinOpKind::BitAnd => (sym::bitand, lang.bitand_trait()),
1049            hir::BinOpKind::BitOr => (sym::bitor, lang.bitor_trait()),
1050            hir::BinOpKind::Shl => (sym::shl, lang.shl_trait()),
1051            hir::BinOpKind::Shr => (sym::shr, lang.shr_trait()),
1052            hir::BinOpKind::Lt => (sym::lt, lang.partial_ord_trait()),
1053            hir::BinOpKind::Le => (sym::le, lang.partial_ord_trait()),
1054            hir::BinOpKind::Ge => (sym::ge, lang.partial_ord_trait()),
1055            hir::BinOpKind::Gt => (sym::gt, lang.partial_ord_trait()),
1056            hir::BinOpKind::Eq => (sym::eq, lang.eq_trait()),
1057            hir::BinOpKind::Ne => (sym::ne, lang.eq_trait()),
1058            hir::BinOpKind::And | hir::BinOpKind::Or => {
1059                bug!("&& and || are not overloadable")
1060            }
1061        },
1062    }
1063}
1064
1065fn lang_item_for_unop(tcx: TyCtxt<'_>, op: hir::UnOp) -> (Symbol, Option<hir::def_id::DefId>) {
1066    let lang = tcx.lang_items();
1067    match op {
1068        hir::UnOp::Not => (sym::not, lang.not_trait()),
1069        hir::UnOp::Neg => (sym::neg, lang.neg_trait()),
1070        hir::UnOp::Deref => bug!("Deref is not overloadable"),
1071    }
1072}
1073
1074// Binary operator categories. These categories summarize the behavior
1075// with respect to the builtin operations supported.
1076#[derive(Clone, Copy)]
1077enum BinOpCategory {
1078    /// &&, || -- cannot be overridden
1079    Shortcircuit,
1080
1081    /// <<, >> -- when shifting a single integer, rhs can be any
1082    /// integer type. For simd, types must match.
1083    Shift,
1084
1085    /// +, -, etc -- takes equal types, produces same type as input,
1086    /// applicable to ints/floats/simd
1087    Math,
1088
1089    /// &, |, ^ -- takes equal types, produces same type as input,
1090    /// applicable to ints/floats/simd/bool
1091    Bitwise,
1092
1093    /// ==, !=, etc -- takes equal types, produces bools, except for simd,
1094    /// which produce the input type
1095    Comparison,
1096}
1097
1098impl From<hir::BinOpKind> for BinOpCategory {
1099    fn from(op: hir::BinOpKind) -> BinOpCategory {
1100        use hir::BinOpKind::*;
1101        match op {
1102            Shl | Shr => BinOpCategory::Shift,
1103            Add | Sub | Mul | Div | Rem => BinOpCategory::Math,
1104            BitXor | BitAnd | BitOr => BinOpCategory::Bitwise,
1105            Eq | Ne | Lt | Le | Ge | Gt => BinOpCategory::Comparison,
1106            And | Or => BinOpCategory::Shortcircuit,
1107        }
1108    }
1109}
1110
1111impl From<hir::AssignOpKind> for BinOpCategory {
1112    fn from(op: hir::AssignOpKind) -> BinOpCategory {
1113        use hir::AssignOpKind::*;
1114        match op {
1115            ShlAssign | ShrAssign => BinOpCategory::Shift,
1116            AddAssign | SubAssign | MulAssign | DivAssign | RemAssign => BinOpCategory::Math,
1117            BitXorAssign | BitAndAssign | BitOrAssign => BinOpCategory::Bitwise,
1118        }
1119    }
1120}
1121
1122/// An assignment op (e.g. `a += b`), or a binary op (e.g. `a + b`).
1123#[derive(Clone, Copy, Debug, PartialEq)]
1124enum Op {
1125    BinOp(hir::BinOp),
1126    AssignOp(hir::AssignOp),
1127}
1128
1129impl Op {
1130    fn span(&self) -> Span {
1131        match self {
1132            Op::BinOp(op) => op.span,
1133            Op::AssignOp(op) => op.span,
1134        }
1135    }
1136
1137    fn as_str(&self) -> &'static str {
1138        match self {
1139            Op::BinOp(op) => op.node.as_str(),
1140            Op::AssignOp(op) => op.node.as_str(),
1141        }
1142    }
1143
1144    fn is_by_value(&self) -> bool {
1145        match self {
1146            Op::BinOp(op) => op.node.is_by_value(),
1147            Op::AssignOp(op) => op.node.is_by_value(),
1148        }
1149    }
1150}
1151
1152/// Dereferences a single level of immutable referencing.
1153fn deref_ty_if_possible(ty: Ty<'_>) -> Ty<'_> {
1154    match ty.kind() {
1155        ty::Ref(_, ty, hir::Mutability::Not) => *ty,
1156        _ => ty,
1157    }
1158}
1159
1160/// Returns `true` if this is a built-in arithmetic operation (e.g., u32
1161/// + u32, i16x4 == i16x4) and false if these types would have to be
1162/// overloaded to be legal. There are two reasons that we distinguish
1163/// builtin operations from overloaded ones (vs trying to drive
1164/// everything uniformly through the trait system and intrinsics or
1165/// something like that):
1166///
1167/// 1. Builtin operations can trivially be evaluated in constants.
1168/// 2. For comparison operators applied to SIMD types the result is
1169///    not of type `bool`. For example, `i16x4 == i16x4` yields a
1170///    type like `i16x4`. This means that the overloaded trait
1171///    `PartialEq` is not applicable.
1172///
1173/// Reason #2 is the killer. I tried for a while to always use
1174/// overloaded logic and just check the types in constants/codegen after
1175/// the fact, and it worked fine, except for SIMD types. -nmatsakis
1176fn is_builtin_binop<'tcx>(lhs: Ty<'tcx>, rhs: Ty<'tcx>, category: BinOpCategory) -> bool {
1177    // Special-case a single layer of referencing, so that things like `5.0 + &6.0f32` work.
1178    // (See https://github.com/rust-lang/rust/issues/57447.)
1179    let (lhs, rhs) = (deref_ty_if_possible(lhs), deref_ty_if_possible(rhs));
1180
1181    match category.into() {
1182        BinOpCategory::Shortcircuit => true,
1183        BinOpCategory::Shift => {
1184            lhs.references_error()
1185                || rhs.references_error()
1186                || lhs.is_integral() && rhs.is_integral()
1187        }
1188        BinOpCategory::Math => {
1189            lhs.references_error()
1190                || rhs.references_error()
1191                || lhs.is_integral() && rhs.is_integral()
1192                || lhs.is_floating_point() && rhs.is_floating_point()
1193        }
1194        BinOpCategory::Bitwise => {
1195            lhs.references_error()
1196                || rhs.references_error()
1197                || lhs.is_integral() && rhs.is_integral()
1198                || lhs.is_floating_point() && rhs.is_floating_point()
1199                || lhs.is_bool() && rhs.is_bool()
1200        }
1201        BinOpCategory::Comparison => {
1202            lhs.references_error() || rhs.references_error() || lhs.is_scalar() && rhs.is_scalar()
1203        }
1204    }
1205}