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