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