• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 //! Code related to processing overloaded binary and unary operators.
2 
3 use super::method::MethodCallee;
4 use super::{has_expected_num_generic_args, FnCtxt};
5 use crate::Expectation;
6 use rustc_ast as ast;
7 use rustc_errors::{self, struct_span_err, Applicability, Diagnostic};
8 use rustc_hir as hir;
9 use rustc_infer::infer::type_variable::{TypeVariableOrigin, TypeVariableOriginKind};
10 use rustc_infer::traits::ObligationCauseCode;
11 use rustc_middle::ty::adjustment::{
12     Adjust, Adjustment, AllowTwoPhase, AutoBorrow, AutoBorrowMutability,
13 };
14 use rustc_middle::ty::print::with_no_trimmed_paths;
15 use rustc_middle::ty::{self, IsSuggestable, Ty, TyCtxt, TypeVisitableExt};
16 use rustc_session::errors::ExprParenthesesNeeded;
17 use rustc_span::source_map::Spanned;
18 use rustc_span::symbol::{sym, Ident};
19 use rustc_span::Span;
20 use rustc_trait_selection::infer::InferCtxtExt;
21 use rustc_trait_selection::traits::error_reporting::suggestions::TypeErrCtxtExt as _;
22 use rustc_trait_selection::traits::{self, FulfillmentError, ObligationCtxt};
23 use rustc_type_ir::sty::TyKind::*;
24 
25 impl<'a, 'tcx> FnCtxt<'a, 'tcx> {
26     /// Checks a `a <op>= b`
check_binop_assign( &self, expr: &'tcx hir::Expr<'tcx>, op: hir::BinOp, lhs: &'tcx hir::Expr<'tcx>, rhs: &'tcx hir::Expr<'tcx>, expected: Expectation<'tcx>, ) -> Ty<'tcx>27     pub fn check_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                 Ty::new_unit(self.tcx)
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_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_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.
check_binop( &self, expr: &'tcx hir::Expr<'tcx>, op: hir::BinOp, lhs_expr: &'tcx hir::Expr<'tcx>, rhs_expr: &'tcx hir::Expr<'tcx>, expected: Expectation<'tcx>, ) -> Ty<'tcx>86     pub fn check_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 
enforce_builtin_binop_types( &self, lhs_span: Span, lhs_ty: Ty<'tcx>, rhs_span: Span, rhs_ty: Ty<'tcx>, op: hir::BinOp, ) -> Ty<'tcx>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 
check_overloaded_binop( &self, expr: &'tcx hir::Expr<'tcx>, lhs_expr: &'tcx hir::Expr<'tcx>, rhs_expr: &'tcx hir::Expr<'tcx>, op: hir::BinOp, is_assign: IsAssign, expected: Expectation<'tcx>, ) -> (Ty<'tcx>, Ty<'tcx>, Ty<'tcx>)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(TypeVariableOrigin {
222                     kind: TypeVariableOriginKind::MiscVariable,
223                     span: lhs_expr.span,
224                 });
225                 self.demand_coerce(lhs_expr, lhs_ty, fresh_var, Some(rhs_expr), AllowTwoPhase::No)
226             }
227             IsAssign::Yes => {
228                 // rust-lang/rust#52126: We have to use strict
229                 // equivalence on the LHS of an assign-op like `+=`;
230                 // overwritten or mutably-borrowed places cannot be
231                 // coerced to a supertype.
232                 self.check_expr(lhs_expr)
233             }
234         };
235         let lhs_ty = self.resolve_vars_with_obligations(lhs_ty);
236 
237         // N.B., as we have not yet type-checked the RHS, we don't have the
238         // type at hand. Make a variable to represent it. The whole reason
239         // for this indirection is so that, below, we can check the expr
240         // using this variable as the expected type, which sometimes lets
241         // us do better coercions than we would be able to do otherwise,
242         // particularly for things like `String + &String`.
243         let rhs_ty_var = self.next_ty_var(TypeVariableOrigin {
244             kind: TypeVariableOriginKind::MiscVariable,
245             span: rhs_expr.span,
246         });
247 
248         let result = self.lookup_op_method(
249             lhs_ty,
250             Some((rhs_expr, rhs_ty_var)),
251             Op::Binary(op, is_assign),
252             expected,
253         );
254 
255         // see `NB` above
256         let rhs_ty = self.check_expr_coercible_to_type(rhs_expr, rhs_ty_var, Some(lhs_expr));
257         let rhs_ty = self.resolve_vars_with_obligations(rhs_ty);
258 
259         let return_ty = match result {
260             Ok(method) => {
261                 let by_ref_binop = !op.node.is_by_value();
262                 if is_assign == IsAssign::Yes || by_ref_binop {
263                     if let ty::Ref(region, _, mutbl) = method.sig.inputs()[0].kind() {
264                         let mutbl = AutoBorrowMutability::new(*mutbl, AllowTwoPhase::Yes);
265                         let autoref = Adjustment {
266                             kind: Adjust::Borrow(AutoBorrow::Ref(*region, mutbl)),
267                             target: method.sig.inputs()[0],
268                         };
269                         self.apply_adjustments(lhs_expr, vec![autoref]);
270                     }
271                 }
272                 if by_ref_binop {
273                     if let ty::Ref(region, _, mutbl) = method.sig.inputs()[1].kind() {
274                         // Allow two-phase borrows for binops in initial deployment
275                         // since they desugar to methods
276                         let mutbl = AutoBorrowMutability::new(*mutbl, AllowTwoPhase::Yes);
277 
278                         let autoref = Adjustment {
279                             kind: Adjust::Borrow(AutoBorrow::Ref(*region, mutbl)),
280                             target: method.sig.inputs()[1],
281                         };
282                         // HACK(eddyb) Bypass checks due to reborrows being in
283                         // some cases applied on the RHS, on top of which we need
284                         // to autoref, which is not allowed by apply_adjustments.
285                         // self.apply_adjustments(rhs_expr, vec![autoref]);
286                         self.typeck_results
287                             .borrow_mut()
288                             .adjustments_mut()
289                             .entry(rhs_expr.hir_id)
290                             .or_default()
291                             .push(autoref);
292                     }
293                 }
294                 self.write_method_call(expr.hir_id, method);
295 
296                 method.sig.output()
297             }
298             // error types are considered "builtin"
299             Err(_) if lhs_ty.references_error() || rhs_ty.references_error() => {
300                 Ty::new_misc_error(self.tcx)
301             }
302             Err(errors) => {
303                 let (_, trait_def_id) =
304                     lang_item_for_op(self.tcx, Op::Binary(op, is_assign), op.span);
305                 let missing_trait = trait_def_id
306                     .map(|def_id| with_no_trimmed_paths!(self.tcx.def_path_str(def_id)));
307                 let (mut err, output_def_id) = match is_assign {
308                     IsAssign::Yes => {
309                         let mut err = struct_span_err!(
310                             self.tcx.sess,
311                             expr.span,
312                             E0368,
313                             "binary assignment operation `{}=` cannot be applied to type `{}`",
314                             op.node.as_str(),
315                             lhs_ty,
316                         );
317                         err.span_label(
318                             lhs_expr.span,
319                             format!("cannot use `{}=` on type `{}`", op.node.as_str(), lhs_ty),
320                         );
321                         self.note_unmet_impls_on_type(&mut err, errors);
322                         (err, None)
323                     }
324                     IsAssign::No => {
325                         let message = match op.node {
326                             hir::BinOpKind::Add => {
327                                 format!("cannot add `{rhs_ty}` to `{lhs_ty}`")
328                             }
329                             hir::BinOpKind::Sub => {
330                                 format!("cannot subtract `{rhs_ty}` from `{lhs_ty}`")
331                             }
332                             hir::BinOpKind::Mul => {
333                                 format!("cannot multiply `{lhs_ty}` by `{rhs_ty}`")
334                             }
335                             hir::BinOpKind::Div => {
336                                 format!("cannot divide `{lhs_ty}` by `{rhs_ty}`")
337                             }
338                             hir::BinOpKind::Rem => {
339                                 format!(
340                                     "cannot calculate the remainder of `{lhs_ty}` divided by `{rhs_ty}`"
341                                 )
342                             }
343                             hir::BinOpKind::BitAnd => {
344                                 format!("no implementation for `{lhs_ty} & {rhs_ty}`")
345                             }
346                             hir::BinOpKind::BitXor => {
347                                 format!("no implementation for `{lhs_ty} ^ {rhs_ty}`")
348                             }
349                             hir::BinOpKind::BitOr => {
350                                 format!("no implementation for `{lhs_ty} | {rhs_ty}`")
351                             }
352                             hir::BinOpKind::Shl => {
353                                 format!("no implementation for `{lhs_ty} << {rhs_ty}`")
354                             }
355                             hir::BinOpKind::Shr => {
356                                 format!("no implementation for `{lhs_ty} >> {rhs_ty}`")
357                             }
358                             _ => format!(
359                                 "binary operation `{}` cannot be applied to type `{}`",
360                                 op.node.as_str(),
361                                 lhs_ty
362                             ),
363                         };
364                         let output_def_id = trait_def_id.and_then(|def_id| {
365                             self.tcx
366                                 .associated_item_def_ids(def_id)
367                                 .iter()
368                                 .find(|item_def_id| {
369                                     self.tcx.associated_item(*item_def_id).name == sym::Output
370                                 })
371                                 .cloned()
372                         });
373                         let mut err = struct_span_err!(self.tcx.sess, op.span, E0369, "{message}");
374                         if !lhs_expr.span.eq(&rhs_expr.span) {
375                             err.span_label(lhs_expr.span, lhs_ty.to_string());
376                             err.span_label(rhs_expr.span, rhs_ty.to_string());
377                         }
378                         self.note_unmet_impls_on_type(&mut err, errors);
379                         (err, output_def_id)
380                     }
381                 };
382 
383                 let mut suggest_deref_binop = |lhs_deref_ty: Ty<'tcx>| {
384                     if self
385                         .lookup_op_method(
386                             lhs_deref_ty,
387                             Some((rhs_expr, rhs_ty)),
388                             Op::Binary(op, is_assign),
389                             expected,
390                         )
391                         .is_ok()
392                     {
393                         let msg = format!(
394                             "`{}{}` can be used on `{}` if you dereference the left-hand side",
395                             op.node.as_str(),
396                             match is_assign {
397                                 IsAssign::Yes => "=",
398                                 IsAssign::No => "",
399                             },
400                             lhs_deref_ty,
401                         );
402                         err.span_suggestion_verbose(
403                             lhs_expr.span.shrink_to_lo(),
404                             msg,
405                             "*",
406                             rustc_errors::Applicability::MachineApplicable,
407                         );
408                     }
409                 };
410 
411                 let is_compatible_after_call = |lhs_ty, rhs_ty| {
412                     self.lookup_op_method(
413                         lhs_ty,
414                         Some((rhs_expr, rhs_ty)),
415                         Op::Binary(op, is_assign),
416                         expected,
417                     )
418                     .is_ok()
419                         // Suggest calling even if, after calling, the types don't
420                         // implement the operator, since it'll lead to better
421                         // diagnostics later.
422                         || self.can_eq(self.param_env, lhs_ty, rhs_ty)
423                 };
424 
425                 // We should suggest `a + b` => `*a + b` if `a` is copy, and suggest
426                 // `a += b` => `*a += b` if a is a mut ref.
427                 if !op.span.can_be_used_for_suggestions() {
428                     // Suppress suggestions when lhs and rhs are not in the same span as the error
429                 } else if is_assign == IsAssign::Yes
430                     && let Some(lhs_deref_ty) = self.deref_once_mutably_for_diagnostic(lhs_ty)
431                 {
432                     suggest_deref_binop(lhs_deref_ty);
433                 } else if is_assign == IsAssign::No
434                     && let Ref(_, lhs_deref_ty, _) = lhs_ty.kind()
435                 {
436                     if self.type_is_copy_modulo_regions(
437                         self.param_env,
438                         *lhs_deref_ty,
439                     ) {
440                         suggest_deref_binop(*lhs_deref_ty);
441                     }
442                 } else if self.suggest_fn_call(&mut err, lhs_expr, lhs_ty, |lhs_ty| {
443                     is_compatible_after_call(lhs_ty, rhs_ty)
444                 }) || self.suggest_fn_call(&mut err, rhs_expr, rhs_ty, |rhs_ty| {
445                     is_compatible_after_call(lhs_ty, rhs_ty)
446                 }) || self.suggest_two_fn_call(
447                     &mut err,
448                     rhs_expr,
449                     rhs_ty,
450                     lhs_expr,
451                     lhs_ty,
452                     |lhs_ty, rhs_ty| is_compatible_after_call(lhs_ty, rhs_ty),
453                 ) {
454                     // Cool
455                 }
456 
457                 if let Some(missing_trait) = missing_trait {
458                     if op.node == hir::BinOpKind::Add
459                         && self.check_str_addition(
460                             lhs_expr, rhs_expr, lhs_ty, rhs_ty, &mut err, is_assign, op,
461                         )
462                     {
463                         // This has nothing here because it means we did string
464                         // concatenation (e.g., "Hello " + "World!"). This means
465                         // we don't want the note in the else clause to be emitted
466                     } else if lhs_ty.has_non_region_param() {
467                         // Look for a TraitPredicate in the Fulfillment errors,
468                         // and use it to generate a suggestion.
469                         //
470                         // Note that lookup_op_method must be called again but
471                         // with a specific rhs_ty instead of a placeholder so
472                         // the resulting predicate generates a more specific
473                         // suggestion for the user.
474                         let errors = self
475                             .lookup_op_method(
476                                 lhs_ty,
477                                 Some((rhs_expr, rhs_ty)),
478                                 Op::Binary(op, is_assign),
479                                 expected,
480                             )
481                             .unwrap_err();
482                         if !errors.is_empty() {
483                             for error in errors {
484                                 if let Some(trait_pred) =
485                                     error.obligation.predicate.to_opt_poly_trait_pred()
486                                 {
487                                     let output_associated_item = match error.obligation.cause.code()
488                                     {
489                                         ObligationCauseCode::BinOp {
490                                             output_ty: Some(output_ty),
491                                             ..
492                                         } => {
493                                             // Make sure that we're attaching `Output = ..` to the right trait predicate
494                                             if let Some(output_def_id) = output_def_id
495                                                 && let Some(trait_def_id) = trait_def_id
496                                                 && self.tcx.parent(output_def_id) == trait_def_id
497                                                 && let Some(output_ty) = output_ty.make_suggestable(self.tcx, false)
498                                             {
499                                                 Some(("Output", output_ty))
500                                             } else {
501                                                 None
502                                             }
503                                         }
504                                         _ => None,
505                                     };
506 
507                                     self.err_ctxt().suggest_restricting_param_bound(
508                                         &mut err,
509                                         trait_pred,
510                                         output_associated_item,
511                                         self.body_id,
512                                     );
513                                 }
514                             }
515                         } else {
516                             // When we know that a missing bound is responsible, we don't show
517                             // this note as it is redundant.
518                             err.note(format!(
519                                 "the trait `{missing_trait}` is not implemented for `{lhs_ty}`"
520                             ));
521                         }
522                     }
523                 }
524 
525                 // Suggest using `add`, `offset` or `offset_from` for pointer - {integer},
526                 // pointer + {integer} or pointer - pointer.
527                 if op.span.can_be_used_for_suggestions() {
528                     match op.node {
529                         hir::BinOpKind::Add if lhs_ty.is_unsafe_ptr() && rhs_ty.is_integral() => {
530                             err.multipart_suggestion(
531                                 "consider using `wrapping_add` or `add` for pointer + {integer}",
532                                 vec![
533                                     (
534                                         lhs_expr.span.between(rhs_expr.span),
535                                         ".wrapping_add(".to_owned(),
536                                     ),
537                                     (rhs_expr.span.shrink_to_hi(), ")".to_owned()),
538                                 ],
539                                 Applicability::MaybeIncorrect,
540                             );
541                         }
542                         hir::BinOpKind::Sub => {
543                             if lhs_ty.is_unsafe_ptr() && rhs_ty.is_integral() {
544                                 err.multipart_suggestion(
545                                     "consider using `wrapping_sub` or `sub` for pointer - {integer}",
546                                     vec![
547                                         (lhs_expr.span.between(rhs_expr.span), ".wrapping_sub(".to_owned()),
548                                         (rhs_expr.span.shrink_to_hi(), ")".to_owned()),
549                                     ],
550                                     Applicability::MaybeIncorrect
551                                 );
552                             }
553 
554                             if lhs_ty.is_unsafe_ptr() && rhs_ty.is_unsafe_ptr() {
555                                 err.multipart_suggestion(
556                                     "consider using `offset_from` for pointer - pointer if the pointers point to the same allocation",
557                                     vec![
558                                         (lhs_expr.span.shrink_to_lo(), "unsafe { ".to_owned()),
559                                         (lhs_expr.span.between(rhs_expr.span), ".offset_from(".to_owned()),
560                                         (rhs_expr.span.shrink_to_hi(), ") }".to_owned()),
561                                     ],
562                                     Applicability::MaybeIncorrect
563                                 );
564                             }
565                         }
566                         _ => {}
567                     }
568                 }
569 
570                 let reported = err.emit();
571                 Ty::new_error(self.tcx, reported)
572             }
573         };
574 
575         (lhs_ty, rhs_ty, return_ty)
576     }
577 
578     /// Provide actionable suggestions when trying to add two strings with incorrect types,
579     /// like `&str + &str`, `String + String` and `&str + &String`.
580     ///
581     /// If this function returns `true` it means a note was printed, so we don't need
582     /// to print the normal "implementation of `std::ops::Add` might be missing" note
check_str_addition( &self, lhs_expr: &'tcx hir::Expr<'tcx>, rhs_expr: &'tcx hir::Expr<'tcx>, lhs_ty: Ty<'tcx>, rhs_ty: Ty<'tcx>, err: &mut Diagnostic, is_assign: IsAssign, op: hir::BinOp, ) -> bool583     fn check_str_addition(
584         &self,
585         lhs_expr: &'tcx hir::Expr<'tcx>,
586         rhs_expr: &'tcx hir::Expr<'tcx>,
587         lhs_ty: Ty<'tcx>,
588         rhs_ty: Ty<'tcx>,
589         err: &mut Diagnostic,
590         is_assign: IsAssign,
591         op: hir::BinOp,
592     ) -> bool {
593         let str_concat_note = "string concatenation requires an owned `String` on the left";
594         let rm_borrow_msg = "remove the borrow to obtain an owned `String`";
595         let to_owned_msg = "create an owned `String` from a string reference";
596 
597         let string_type = self.tcx.lang_items().string();
598         let is_std_string =
599             |ty: Ty<'tcx>| ty.ty_adt_def().is_some_and(|ty_def| Some(ty_def.did()) == string_type);
600 
601         match (lhs_ty.kind(), rhs_ty.kind()) {
602             (&Ref(_, l_ty, _), &Ref(_, r_ty, _)) // &str or &String + &str, &String or &&str
603                 if (*l_ty.kind() == Str || is_std_string(l_ty))
604                     && (*r_ty.kind() == Str
605                         || is_std_string(r_ty)
606                         || matches!(
607                             r_ty.kind(), Ref(_, inner_ty, _) if *inner_ty.kind() == Str
608                         )) =>
609             {
610                 if let IsAssign::No = is_assign { // Do not supply this message if `&str += &str`
611                     err.span_label(op.span, "`+` cannot be used to concatenate two `&str` strings");
612                     err.note(str_concat_note);
613                     if let hir::ExprKind::AddrOf(_, _, lhs_inner_expr) = lhs_expr.kind {
614                         err.span_suggestion_verbose(
615                             lhs_expr.span.until(lhs_inner_expr.span),
616                             rm_borrow_msg,
617                             "",
618                             Applicability::MachineApplicable
619                         );
620                     } else {
621                         err.span_suggestion_verbose(
622                             lhs_expr.span.shrink_to_hi(),
623                             to_owned_msg,
624                             ".to_owned()",
625                             Applicability::MachineApplicable
626                         );
627                     }
628                 }
629                 true
630             }
631             (&Ref(_, l_ty, _), &Adt(..)) // Handle `&str` & `&String` + `String`
632                 if (*l_ty.kind() == Str || is_std_string(l_ty)) && is_std_string(rhs_ty) =>
633             {
634                 err.span_label(
635                     op.span,
636                     "`+` cannot be used to concatenate a `&str` with a `String`",
637                 );
638                 match is_assign {
639                     IsAssign::No => {
640                         let sugg_msg;
641                         let lhs_sugg = if let hir::ExprKind::AddrOf(_, _, lhs_inner_expr) = lhs_expr.kind {
642                             sugg_msg = "remove the borrow on the left and add one on the right";
643                             (lhs_expr.span.until(lhs_inner_expr.span), "".to_owned())
644                         } else {
645                             sugg_msg = "create an owned `String` on the left and add a borrow on the right";
646                             (lhs_expr.span.shrink_to_hi(), ".to_owned()".to_owned())
647                         };
648                         let suggestions = vec![
649                             lhs_sugg,
650                             (rhs_expr.span.shrink_to_lo(), "&".to_owned()),
651                         ];
652                         err.multipart_suggestion_verbose(
653                             sugg_msg,
654                             suggestions,
655                             Applicability::MachineApplicable,
656                         );
657                     }
658                     IsAssign::Yes => {
659                         err.note(str_concat_note);
660                     }
661                 }
662                 true
663             }
664             _ => false,
665         }
666     }
667 
check_user_unop( &self, ex: &'tcx hir::Expr<'tcx>, operand_ty: Ty<'tcx>, op: hir::UnOp, expected: Expectation<'tcx>, ) -> Ty<'tcx>668     pub fn check_user_unop(
669         &self,
670         ex: &'tcx hir::Expr<'tcx>,
671         operand_ty: Ty<'tcx>,
672         op: hir::UnOp,
673         expected: Expectation<'tcx>,
674     ) -> Ty<'tcx> {
675         assert!(op.is_by_value());
676         match self.lookup_op_method(operand_ty, None, Op::Unary(op, ex.span), expected) {
677             Ok(method) => {
678                 self.write_method_call(ex.hir_id, method);
679                 method.sig.output()
680             }
681             Err(errors) => {
682                 let actual = self.resolve_vars_if_possible(operand_ty);
683                 let guar = actual.error_reported().err().unwrap_or_else(|| {
684                     let mut err = struct_span_err!(
685                         self.tcx.sess,
686                         ex.span,
687                         E0600,
688                         "cannot apply unary operator `{}` to type `{}`",
689                         op.as_str(),
690                         actual
691                     );
692                     err.span_label(
693                         ex.span,
694                         format!("cannot apply unary operator `{}`", op.as_str()),
695                     );
696 
697                     if operand_ty.has_non_region_param() {
698                         let predicates = errors.iter().filter_map(|error| {
699                             error.obligation.predicate.to_opt_poly_trait_pred()
700                         });
701                         for pred in predicates {
702                             self.err_ctxt().suggest_restricting_param_bound(
703                                 &mut err,
704                                 pred,
705                                 None,
706                                 self.body_id,
707                             );
708                         }
709                     }
710 
711                     let sp = self.tcx.sess.source_map().start_point(ex.span).with_parent(None);
712                     if let Some(sp) =
713                         self.tcx.sess.parse_sess.ambiguous_block_expr_parse.borrow().get(&sp)
714                     {
715                         // If the previous expression was a block expression, suggest parentheses
716                         // (turning this into a binary subtraction operation instead.)
717                         // for example, `{2} - 2` -> `({2}) - 2` (see src\test\ui\parser\expr-as-stmt.rs)
718                         err.subdiagnostic(ExprParenthesesNeeded::surrounding(*sp));
719                     } else {
720                         match actual.kind() {
721                             Uint(_) if op == hir::UnOp::Neg => {
722                                 err.note("unsigned values cannot be negated");
723 
724                                 if let hir::ExprKind::Unary(
725                                     _,
726                                     hir::Expr {
727                                         kind:
728                                             hir::ExprKind::Lit(Spanned {
729                                                 node: ast::LitKind::Int(1, _),
730                                                 ..
731                                             }),
732                                         ..
733                                     },
734                                 ) = ex.kind
735                                 {
736                                     err.span_suggestion(
737                                         ex.span,
738                                         format!(
739                                             "you may have meant the maximum value of `{actual}`",
740                                         ),
741                                         format!("{actual}::MAX"),
742                                         Applicability::MaybeIncorrect,
743                                     );
744                                 }
745                             }
746                             Str | Never | Char | Tuple(_) | Array(_, _) => {}
747                             Ref(_, lty, _) if *lty.kind() == Str => {}
748                             _ => {
749                                 self.note_unmet_impls_on_type(&mut err, errors);
750                             }
751                         }
752                     }
753                     err.emit()
754                 });
755                 Ty::new_error(self.tcx, guar)
756             }
757         }
758     }
759 
lookup_op_method( &self, lhs_ty: Ty<'tcx>, opt_rhs: Option<(&'tcx hir::Expr<'tcx>, Ty<'tcx>)>, op: Op, expected: Expectation<'tcx>, ) -> Result<MethodCallee<'tcx>, Vec<FulfillmentError<'tcx>>>760     fn lookup_op_method(
761         &self,
762         lhs_ty: Ty<'tcx>,
763         opt_rhs: Option<(&'tcx hir::Expr<'tcx>, Ty<'tcx>)>,
764         op: Op,
765         expected: Expectation<'tcx>,
766     ) -> Result<MethodCallee<'tcx>, Vec<FulfillmentError<'tcx>>> {
767         let span = match op {
768             Op::Binary(op, _) => op.span,
769             Op::Unary(_, span) => span,
770         };
771         let (opname, Some(trait_did)) = lang_item_for_op(self.tcx, op, span) else {
772             // Bail if the operator trait is not defined.
773             return Err(vec![]);
774         };
775 
776         debug!(
777             "lookup_op_method(lhs_ty={:?}, op={:?}, opname={:?}, trait_did={:?})",
778             lhs_ty, op, opname, trait_did
779         );
780 
781         // Catches cases like #83893, where a lang item is declared with the
782         // wrong number of generic arguments. Should have yielded an error
783         // elsewhere by now, but we have to catch it here so that we do not
784         // index `other_tys` out of bounds (if the lang item has too many
785         // generic arguments, `other_tys` is too short).
786         if !has_expected_num_generic_args(
787             self.tcx,
788             trait_did,
789             match op {
790                 // Binary ops have a generic right-hand side, unary ops don't
791                 Op::Binary(..) => 1,
792                 Op::Unary(..) => 0,
793             },
794         ) {
795             self.tcx
796                 .sess
797                 .delay_span_bug(span, "operator didn't have the right number of generic args");
798             return Err(vec![]);
799         }
800 
801         let opname = Ident::with_dummy_span(opname);
802         let (opt_rhs_expr, opt_rhs_ty) = opt_rhs.unzip();
803         let input_types = opt_rhs_ty.as_slice();
804         let cause = self.cause(
805             span,
806             traits::BinOp {
807                 rhs_span: opt_rhs_expr.map(|expr| expr.span),
808                 is_lit: opt_rhs_expr.is_some_and(|expr| matches!(expr.kind, hir::ExprKind::Lit(_))),
809                 output_ty: expected.only_has_type(self),
810             },
811         );
812 
813         let method = self.lookup_method_in_trait(
814             cause.clone(),
815             opname,
816             trait_did,
817             lhs_ty,
818             Some(input_types),
819         );
820         match method {
821             Some(ok) => {
822                 let method = self.register_infer_ok_obligations(ok);
823                 self.select_obligations_where_possible(|_| {});
824                 Ok(method)
825             }
826             None => {
827                 // This path may do some inference, so make sure we've really
828                 // doomed compilation so as to not accidentally stabilize new
829                 // inference or something here...
830                 self.tcx.sess.delay_span_bug(span, "this path really should be doomed...");
831                 // Guide inference for the RHS expression if it's provided --
832                 // this will allow us to better error reporting, at the expense
833                 // of making some error messages a bit more specific.
834                 if let Some((rhs_expr, rhs_ty)) = opt_rhs
835                     && rhs_ty.is_ty_var()
836                 {
837                     self.check_expr_coercible_to_type(rhs_expr, rhs_ty, None);
838                 }
839 
840                 let (obligation, _) =
841                     self.obligation_for_method(cause, trait_did, lhs_ty, Some(input_types));
842                 // FIXME: This should potentially just add the obligation to the `FnCtxt`
843                 let ocx = ObligationCtxt::new(&self.infcx);
844                 ocx.register_obligation(obligation);
845                 Err(ocx.select_all_or_error())
846             }
847         }
848     }
849 }
850 
lang_item_for_op( tcx: TyCtxt<'_>, op: Op, span: Span, ) -> (rustc_span::Symbol, Option<hir::def_id::DefId>)851 fn lang_item_for_op(
852     tcx: TyCtxt<'_>,
853     op: Op,
854     span: Span,
855 ) -> (rustc_span::Symbol, Option<hir::def_id::DefId>) {
856     let lang = tcx.lang_items();
857     if let Op::Binary(op, IsAssign::Yes) = op {
858         match op.node {
859             hir::BinOpKind::Add => (sym::add_assign, lang.add_assign_trait()),
860             hir::BinOpKind::Sub => (sym::sub_assign, lang.sub_assign_trait()),
861             hir::BinOpKind::Mul => (sym::mul_assign, lang.mul_assign_trait()),
862             hir::BinOpKind::Div => (sym::div_assign, lang.div_assign_trait()),
863             hir::BinOpKind::Rem => (sym::rem_assign, lang.rem_assign_trait()),
864             hir::BinOpKind::BitXor => (sym::bitxor_assign, lang.bitxor_assign_trait()),
865             hir::BinOpKind::BitAnd => (sym::bitand_assign, lang.bitand_assign_trait()),
866             hir::BinOpKind::BitOr => (sym::bitor_assign, lang.bitor_assign_trait()),
867             hir::BinOpKind::Shl => (sym::shl_assign, lang.shl_assign_trait()),
868             hir::BinOpKind::Shr => (sym::shr_assign, lang.shr_assign_trait()),
869             hir::BinOpKind::Lt
870             | hir::BinOpKind::Le
871             | hir::BinOpKind::Ge
872             | hir::BinOpKind::Gt
873             | hir::BinOpKind::Eq
874             | hir::BinOpKind::Ne
875             | hir::BinOpKind::And
876             | hir::BinOpKind::Or => {
877                 span_bug!(span, "impossible assignment operation: {}=", op.node.as_str())
878             }
879         }
880     } else if let Op::Binary(op, IsAssign::No) = op {
881         match op.node {
882             hir::BinOpKind::Add => (sym::add, lang.add_trait()),
883             hir::BinOpKind::Sub => (sym::sub, lang.sub_trait()),
884             hir::BinOpKind::Mul => (sym::mul, lang.mul_trait()),
885             hir::BinOpKind::Div => (sym::div, lang.div_trait()),
886             hir::BinOpKind::Rem => (sym::rem, lang.rem_trait()),
887             hir::BinOpKind::BitXor => (sym::bitxor, lang.bitxor_trait()),
888             hir::BinOpKind::BitAnd => (sym::bitand, lang.bitand_trait()),
889             hir::BinOpKind::BitOr => (sym::bitor, lang.bitor_trait()),
890             hir::BinOpKind::Shl => (sym::shl, lang.shl_trait()),
891             hir::BinOpKind::Shr => (sym::shr, lang.shr_trait()),
892             hir::BinOpKind::Lt => (sym::lt, lang.partial_ord_trait()),
893             hir::BinOpKind::Le => (sym::le, lang.partial_ord_trait()),
894             hir::BinOpKind::Ge => (sym::ge, lang.partial_ord_trait()),
895             hir::BinOpKind::Gt => (sym::gt, lang.partial_ord_trait()),
896             hir::BinOpKind::Eq => (sym::eq, lang.eq_trait()),
897             hir::BinOpKind::Ne => (sym::ne, lang.eq_trait()),
898             hir::BinOpKind::And | hir::BinOpKind::Or => {
899                 span_bug!(span, "&& and || are not overloadable")
900             }
901         }
902     } else if let Op::Unary(hir::UnOp::Not, _) = op {
903         (sym::not, lang.not_trait())
904     } else if let Op::Unary(hir::UnOp::Neg, _) = op {
905         (sym::neg, lang.neg_trait())
906     } else {
907         bug!("lookup_op_method: op not supported: {:?}", op)
908     }
909 }
910 
911 // Binary operator categories. These categories summarize the behavior
912 // with respect to the builtin operations supported.
913 enum BinOpCategory {
914     /// &&, || -- cannot be overridden
915     Shortcircuit,
916 
917     /// <<, >> -- when shifting a single integer, rhs can be any
918     /// integer type. For simd, types must match.
919     Shift,
920 
921     /// +, -, etc -- takes equal types, produces same type as input,
922     /// applicable to ints/floats/simd
923     Math,
924 
925     /// &, |, ^ -- takes equal types, produces same type as input,
926     /// applicable to ints/floats/simd/bool
927     Bitwise,
928 
929     /// ==, !=, etc -- takes equal types, produces bools, except for simd,
930     /// which produce the input type
931     Comparison,
932 }
933 
934 impl BinOpCategory {
from(op: hir::BinOp) -> BinOpCategory935     fn from(op: hir::BinOp) -> BinOpCategory {
936         match op.node {
937             hir::BinOpKind::Shl | hir::BinOpKind::Shr => BinOpCategory::Shift,
938 
939             hir::BinOpKind::Add
940             | hir::BinOpKind::Sub
941             | hir::BinOpKind::Mul
942             | hir::BinOpKind::Div
943             | hir::BinOpKind::Rem => BinOpCategory::Math,
944 
945             hir::BinOpKind::BitXor | hir::BinOpKind::BitAnd | hir::BinOpKind::BitOr => {
946                 BinOpCategory::Bitwise
947             }
948 
949             hir::BinOpKind::Eq
950             | hir::BinOpKind::Ne
951             | hir::BinOpKind::Lt
952             | hir::BinOpKind::Le
953             | hir::BinOpKind::Ge
954             | hir::BinOpKind::Gt => BinOpCategory::Comparison,
955 
956             hir::BinOpKind::And | hir::BinOpKind::Or => BinOpCategory::Shortcircuit,
957         }
958     }
959 }
960 
961 /// Whether the binary operation is an assignment (`a += b`), or not (`a + b`)
962 #[derive(Clone, Copy, Debug, PartialEq)]
963 enum IsAssign {
964     No,
965     Yes,
966 }
967 
968 #[derive(Clone, Copy, Debug)]
969 enum Op {
970     Binary(hir::BinOp, IsAssign),
971     Unary(hir::UnOp, Span),
972 }
973 
974 /// Dereferences a single level of immutable referencing.
deref_ty_if_possible(ty: Ty<'_>) -> Ty<'_>975 fn deref_ty_if_possible(ty: Ty<'_>) -> Ty<'_> {
976     match ty.kind() {
977         ty::Ref(_, ty, hir::Mutability::Not) => *ty,
978         _ => ty,
979     }
980 }
981 
982 /// Returns `true` if this is a built-in arithmetic operation (e.g., u32
983 /// + u32, i16x4 == i16x4) and false if these types would have to be
984 /// overloaded to be legal. There are two reasons that we distinguish
985 /// builtin operations from overloaded ones (vs trying to drive
986 /// everything uniformly through the trait system and intrinsics or
987 /// something like that):
988 ///
989 /// 1. Builtin operations can trivially be evaluated in constants.
990 /// 2. For comparison operators applied to SIMD types the result is
991 ///    not of type `bool`. For example, `i16x4 == i16x4` yields a
992 ///    type like `i16x4`. This means that the overloaded trait
993 ///    `PartialEq` is not applicable.
994 ///
995 /// Reason #2 is the killer. I tried for a while to always use
996 /// overloaded logic and just check the types in constants/codegen after
997 /// the fact, and it worked fine, except for SIMD types. -nmatsakis
is_builtin_binop<'tcx>(lhs: Ty<'tcx>, rhs: Ty<'tcx>, op: hir::BinOp) -> bool998 fn is_builtin_binop<'tcx>(lhs: Ty<'tcx>, rhs: Ty<'tcx>, op: hir::BinOp) -> bool {
999     // Special-case a single layer of referencing, so that things like `5.0 + &6.0f32` work.
1000     // (See https://github.com/rust-lang/rust/issues/57447.)
1001     let (lhs, rhs) = (deref_ty_if_possible(lhs), deref_ty_if_possible(rhs));
1002 
1003     match BinOpCategory::from(op) {
1004         BinOpCategory::Shortcircuit => true,
1005 
1006         BinOpCategory::Shift => {
1007             lhs.references_error()
1008                 || rhs.references_error()
1009                 || lhs.is_integral() && rhs.is_integral()
1010         }
1011 
1012         BinOpCategory::Math => {
1013             lhs.references_error()
1014                 || rhs.references_error()
1015                 || lhs.is_integral() && rhs.is_integral()
1016                 || lhs.is_floating_point() && rhs.is_floating_point()
1017         }
1018 
1019         BinOpCategory::Bitwise => {
1020             lhs.references_error()
1021                 || rhs.references_error()
1022                 || lhs.is_integral() && rhs.is_integral()
1023                 || lhs.is_floating_point() && rhs.is_floating_point()
1024                 || lhs.is_bool() && rhs.is_bool()
1025         }
1026 
1027         BinOpCategory::Comparison => {
1028             lhs.references_error() || rhs.references_error() || lhs.is_scalar() && rhs.is_scalar()
1029         }
1030     }
1031 }
1032