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