summaryrefslogtreecommitdiffstats
path: root/compiler/rustc_typeck/src/check/op.rs
diff options
context:
space:
mode:
Diffstat (limited to '')
-rw-r--r--compiler/rustc_typeck/src/check/op.rs1076
1 files changed, 0 insertions, 1076 deletions
diff --git a/compiler/rustc_typeck/src/check/op.rs b/compiler/rustc_typeck/src/check/op.rs
deleted file mode 100644
index 920b3e688..000000000
--- a/compiler/rustc_typeck/src/check/op.rs
+++ /dev/null
@@ -1,1076 +0,0 @@
-//! Code related to processing overloaded binary and unary operators.
-
-use super::method::MethodCallee;
-use super::{has_expected_num_generic_args, FnCtxt};
-use crate::check::Expectation;
-use rustc_ast as ast;
-use rustc_errors::{self, struct_span_err, Applicability, Diagnostic};
-use rustc_hir as hir;
-use rustc_infer::infer::type_variable::{TypeVariableOrigin, TypeVariableOriginKind};
-use rustc_infer::traits::ObligationCauseCode;
-use rustc_middle::ty::adjustment::{
- Adjust, Adjustment, AllowTwoPhase, AutoBorrow, AutoBorrowMutability,
-};
-use rustc_middle::ty::{
- self, Ty, TyCtxt, TypeFolder, TypeSuperFoldable, TypeSuperVisitable, TypeVisitable, TypeVisitor,
-};
-use rustc_span::source_map::Spanned;
-use rustc_span::symbol::{sym, Ident};
-use rustc_span::Span;
-use rustc_trait_selection::infer::InferCtxtExt;
-use rustc_trait_selection::traits::error_reporting::suggestions::InferCtxtExt as _;
-use rustc_trait_selection::traits::{FulfillmentError, TraitEngine, TraitEngineExt};
-use rustc_type_ir::sty::TyKind::*;
-
-use std::ops::ControlFlow;
-
-impl<'a, 'tcx> FnCtxt<'a, 'tcx> {
- /// Checks a `a <op>= b`
- pub fn 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> {
- let (lhs_ty, rhs_ty, return_ty) =
- self.check_overloaded_binop(expr, lhs, rhs, op, IsAssign::Yes, expected);
-
- let ty =
- if !lhs_ty.is_ty_var() && !rhs_ty.is_ty_var() && is_builtin_binop(lhs_ty, rhs_ty, op) {
- self.enforce_builtin_binop_types(lhs.span, lhs_ty, rhs.span, rhs_ty, op);
- self.tcx.mk_unit()
- } else {
- return_ty
- };
-
- self.check_lhs_assignable(lhs, "E0067", op.span, |err| {
- if let Some(lhs_deref_ty) = self.deref_once_mutably_for_diagnostic(lhs_ty) {
- if self
- .lookup_op_method(
- lhs_deref_ty,
- Some(rhs_ty),
- Some(rhs),
- Op::Binary(op, IsAssign::Yes),
- expected,
- )
- .is_ok()
- {
- // Suppress this error, since we already emitted
- // a deref suggestion in check_overloaded_binop
- err.delay_as_bug();
- }
- }
- });
-
- ty
- }
-
- /// Checks a potentially overloaded binary operator.
- pub fn 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> {
- let tcx = self.tcx;
-
- debug!(
- "check_binop(expr.hir_id={}, expr={:?}, op={:?}, lhs_expr={:?}, rhs_expr={:?})",
- expr.hir_id, expr, op, lhs_expr, rhs_expr
- );
-
- match BinOpCategory::from(op) {
- BinOpCategory::Shortcircuit => {
- // && and || are a simple case.
- self.check_expr_coercable_to_type(lhs_expr, tcx.types.bool, None);
- let lhs_diverges = self.diverges.get();
- self.check_expr_coercable_to_type(rhs_expr, tcx.types.bool, None);
-
- // Depending on the LHS' value, the RHS can never execute.
- self.diverges.set(lhs_diverges);
-
- tcx.types.bool
- }
- _ => {
- // Otherwise, we always treat operators as if they are
- // overloaded. This is the way to be most flexible w/r/t
- // types that get inferred.
- let (lhs_ty, rhs_ty, return_ty) = self.check_overloaded_binop(
- expr,
- lhs_expr,
- rhs_expr,
- op,
- IsAssign::No,
- expected,
- );
-
- // Supply type inference hints if relevant. Probably these
- // hints should be enforced during select as part of the
- // `consider_unification_despite_ambiguity` routine, but this
- // more convenient for now.
- //
- // The basic idea is to help type inference by taking
- // advantage of things we know about how the impls for
- // scalar types are arranged. This is important in a
- // scenario like `1_u32 << 2`, because it lets us quickly
- // deduce that the result type should be `u32`, even
- // though we don't know yet what type 2 has and hence
- // can't pin this down to a specific impl.
- if !lhs_ty.is_ty_var()
- && !rhs_ty.is_ty_var()
- && is_builtin_binop(lhs_ty, rhs_ty, op)
- {
- let builtin_return_ty = self.enforce_builtin_binop_types(
- lhs_expr.span,
- lhs_ty,
- rhs_expr.span,
- rhs_ty,
- op,
- );
- self.demand_suptype(expr.span, builtin_return_ty, return_ty);
- }
-
- return_ty
- }
- }
- }
-
- fn enforce_builtin_binop_types(
- &self,
- lhs_span: Span,
- lhs_ty: Ty<'tcx>,
- rhs_span: Span,
- rhs_ty: Ty<'tcx>,
- op: hir::BinOp,
- ) -> Ty<'tcx> {
- debug_assert!(is_builtin_binop(lhs_ty, rhs_ty, op));
-
- // Special-case a single layer of referencing, so that things like `5.0 + &6.0f32` work.
- // (See https://github.com/rust-lang/rust/issues/57447.)
- let (lhs_ty, rhs_ty) = (deref_ty_if_possible(lhs_ty), deref_ty_if_possible(rhs_ty));
-
- let tcx = self.tcx;
- match BinOpCategory::from(op) {
- BinOpCategory::Shortcircuit => {
- self.demand_suptype(lhs_span, tcx.types.bool, lhs_ty);
- self.demand_suptype(rhs_span, tcx.types.bool, rhs_ty);
- tcx.types.bool
- }
-
- BinOpCategory::Shift => {
- // result type is same as LHS always
- lhs_ty
- }
-
- BinOpCategory::Math | BinOpCategory::Bitwise => {
- // both LHS and RHS and result will have the same type
- self.demand_suptype(rhs_span, lhs_ty, rhs_ty);
- lhs_ty
- }
-
- BinOpCategory::Comparison => {
- // both LHS and RHS and result will have the same type
- self.demand_suptype(rhs_span, lhs_ty, rhs_ty);
- tcx.types.bool
- }
- }
- }
-
- fn 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>) {
- debug!(
- "check_overloaded_binop(expr.hir_id={}, op={:?}, is_assign={:?})",
- expr.hir_id, op, is_assign
- );
-
- let lhs_ty = match is_assign {
- IsAssign::No => {
- // Find a suitable supertype of the LHS expression's type, by coercing to
- // a type variable, to pass as the `Self` to the trait, avoiding invariant
- // trait matching creating lifetime constraints that are too strict.
- // e.g., adding `&'a T` and `&'b T`, given `&'x T: Add<&'x T>`, will result
- // in `&'a T <: &'x T` and `&'b T <: &'x T`, instead of `'a = 'b = 'x`.
- let lhs_ty = self.check_expr(lhs_expr);
- let fresh_var = self.next_ty_var(TypeVariableOrigin {
- kind: TypeVariableOriginKind::MiscVariable,
- span: lhs_expr.span,
- });
- self.demand_coerce(lhs_expr, lhs_ty, fresh_var, Some(rhs_expr), AllowTwoPhase::No)
- }
- IsAssign::Yes => {
- // rust-lang/rust#52126: We have to use strict
- // equivalence on the LHS of an assign-op like `+=`;
- // overwritten or mutably-borrowed places cannot be
- // coerced to a supertype.
- self.check_expr(lhs_expr)
- }
- };
- let lhs_ty = self.resolve_vars_with_obligations(lhs_ty);
-
- // N.B., as we have not yet type-checked the RHS, we don't have the
- // type at hand. Make a variable to represent it. The whole reason
- // for this indirection is so that, below, we can check the expr
- // using this variable as the expected type, which sometimes lets
- // us do better coercions than we would be able to do otherwise,
- // particularly for things like `String + &String`.
- let rhs_ty_var = self.next_ty_var(TypeVariableOrigin {
- kind: TypeVariableOriginKind::MiscVariable,
- span: rhs_expr.span,
- });
-
- let result = self.lookup_op_method(
- lhs_ty,
- Some(rhs_ty_var),
- Some(rhs_expr),
- Op::Binary(op, is_assign),
- expected,
- );
-
- // see `NB` above
- let rhs_ty = self.check_expr_coercable_to_type(rhs_expr, rhs_ty_var, Some(lhs_expr));
- let rhs_ty = self.resolve_vars_with_obligations(rhs_ty);
-
- let return_ty = match result {
- Ok(method) => {
- let by_ref_binop = !op.node.is_by_value();
- if is_assign == IsAssign::Yes || by_ref_binop {
- if let ty::Ref(region, _, mutbl) = method.sig.inputs()[0].kind() {
- let mutbl = match mutbl {
- hir::Mutability::Not => AutoBorrowMutability::Not,
- hir::Mutability::Mut => AutoBorrowMutability::Mut {
- // Allow two-phase borrows for binops in initial deployment
- // since they desugar to methods
- allow_two_phase_borrow: AllowTwoPhase::Yes,
- },
- };
- let autoref = Adjustment {
- kind: Adjust::Borrow(AutoBorrow::Ref(*region, mutbl)),
- target: method.sig.inputs()[0],
- };
- self.apply_adjustments(lhs_expr, vec![autoref]);
- }
- }
- if by_ref_binop {
- if let ty::Ref(region, _, mutbl) = method.sig.inputs()[1].kind() {
- let mutbl = match mutbl {
- hir::Mutability::Not => AutoBorrowMutability::Not,
- hir::Mutability::Mut => AutoBorrowMutability::Mut {
- // Allow two-phase borrows for binops in initial deployment
- // since they desugar to methods
- allow_two_phase_borrow: AllowTwoPhase::Yes,
- },
- };
- let autoref = Adjustment {
- kind: Adjust::Borrow(AutoBorrow::Ref(*region, mutbl)),
- target: method.sig.inputs()[1],
- };
- // HACK(eddyb) Bypass checks due to reborrows being in
- // some cases applied on the RHS, on top of which we need
- // to autoref, which is not allowed by apply_adjustments.
- // self.apply_adjustments(rhs_expr, vec![autoref]);
- self.typeck_results
- .borrow_mut()
- .adjustments_mut()
- .entry(rhs_expr.hir_id)
- .or_default()
- .push(autoref);
- }
- }
- self.write_method_call(expr.hir_id, method);
-
- method.sig.output()
- }
- // error types are considered "builtin"
- Err(_) if lhs_ty.references_error() || rhs_ty.references_error() => self.tcx.ty_error(),
- Err(errors) => {
- let source_map = self.tcx.sess.source_map();
- let (mut err, missing_trait, use_output) = match is_assign {
- IsAssign::Yes => {
- let mut err = struct_span_err!(
- self.tcx.sess,
- expr.span,
- E0368,
- "binary assignment operation `{}=` cannot be applied to type `{}`",
- op.node.as_str(),
- lhs_ty,
- );
- err.span_label(
- lhs_expr.span,
- format!("cannot use `{}=` on type `{}`", op.node.as_str(), lhs_ty),
- );
- let missing_trait = match op.node {
- hir::BinOpKind::Add => Some("std::ops::AddAssign"),
- hir::BinOpKind::Sub => Some("std::ops::SubAssign"),
- hir::BinOpKind::Mul => Some("std::ops::MulAssign"),
- hir::BinOpKind::Div => Some("std::ops::DivAssign"),
- hir::BinOpKind::Rem => Some("std::ops::RemAssign"),
- hir::BinOpKind::BitAnd => Some("std::ops::BitAndAssign"),
- hir::BinOpKind::BitXor => Some("std::ops::BitXorAssign"),
- hir::BinOpKind::BitOr => Some("std::ops::BitOrAssign"),
- hir::BinOpKind::Shl => Some("std::ops::ShlAssign"),
- hir::BinOpKind::Shr => Some("std::ops::ShrAssign"),
- _ => None,
- };
- self.note_unmet_impls_on_type(&mut err, errors);
- (err, missing_trait, false)
- }
- IsAssign::No => {
- let (message, missing_trait, use_output) = match op.node {
- hir::BinOpKind::Add => (
- format!("cannot add `{rhs_ty}` to `{lhs_ty}`"),
- Some("std::ops::Add"),
- true,
- ),
- hir::BinOpKind::Sub => (
- format!("cannot subtract `{rhs_ty}` from `{lhs_ty}`"),
- Some("std::ops::Sub"),
- true,
- ),
- hir::BinOpKind::Mul => (
- format!("cannot multiply `{lhs_ty}` by `{rhs_ty}`"),
- Some("std::ops::Mul"),
- true,
- ),
- hir::BinOpKind::Div => (
- format!("cannot divide `{lhs_ty}` by `{rhs_ty}`"),
- Some("std::ops::Div"),
- true,
- ),
- hir::BinOpKind::Rem => (
- format!("cannot mod `{lhs_ty}` by `{rhs_ty}`"),
- Some("std::ops::Rem"),
- true,
- ),
- hir::BinOpKind::BitAnd => (
- format!("no implementation for `{lhs_ty} & {rhs_ty}`"),
- Some("std::ops::BitAnd"),
- true,
- ),
- hir::BinOpKind::BitXor => (
- format!("no implementation for `{lhs_ty} ^ {rhs_ty}`"),
- Some("std::ops::BitXor"),
- true,
- ),
- hir::BinOpKind::BitOr => (
- format!("no implementation for `{lhs_ty} | {rhs_ty}`"),
- Some("std::ops::BitOr"),
- true,
- ),
- hir::BinOpKind::Shl => (
- format!("no implementation for `{lhs_ty} << {rhs_ty}`"),
- Some("std::ops::Shl"),
- true,
- ),
- hir::BinOpKind::Shr => (
- format!("no implementation for `{lhs_ty} >> {rhs_ty}`"),
- Some("std::ops::Shr"),
- true,
- ),
- hir::BinOpKind::Eq | hir::BinOpKind::Ne => (
- format!(
- "binary operation `{}` cannot be applied to type `{}`",
- op.node.as_str(),
- lhs_ty
- ),
- Some("std::cmp::PartialEq"),
- false,
- ),
- hir::BinOpKind::Lt
- | hir::BinOpKind::Le
- | hir::BinOpKind::Gt
- | hir::BinOpKind::Ge => (
- format!(
- "binary operation `{}` cannot be applied to type `{}`",
- op.node.as_str(),
- lhs_ty
- ),
- Some("std::cmp::PartialOrd"),
- false,
- ),
- _ => (
- format!(
- "binary operation `{}` cannot be applied to type `{}`",
- op.node.as_str(),
- lhs_ty
- ),
- None,
- false,
- ),
- };
- let mut err = struct_span_err!(self.tcx.sess, op.span, E0369, "{message}");
- if !lhs_expr.span.eq(&rhs_expr.span) {
- self.add_type_neq_err_label(
- &mut err,
- lhs_expr.span,
- lhs_ty,
- rhs_ty,
- rhs_expr,
- op,
- is_assign,
- expected,
- );
- self.add_type_neq_err_label(
- &mut err,
- rhs_expr.span,
- rhs_ty,
- lhs_ty,
- lhs_expr,
- op,
- is_assign,
- expected,
- );
- }
- self.note_unmet_impls_on_type(&mut err, errors);
- (err, missing_trait, use_output)
- }
- };
-
- let mut suggest_deref_binop = |lhs_deref_ty: Ty<'tcx>| {
- if self
- .lookup_op_method(
- lhs_deref_ty,
- Some(rhs_ty),
- Some(rhs_expr),
- Op::Binary(op, is_assign),
- expected,
- )
- .is_ok()
- {
- if let Ok(lstring) = source_map.span_to_snippet(lhs_expr.span) {
- let msg = &format!(
- "`{}{}` can be used on `{}`, you can dereference `{}`",
- op.node.as_str(),
- match is_assign {
- IsAssign::Yes => "=",
- IsAssign::No => "",
- },
- lhs_deref_ty.peel_refs(),
- lstring,
- );
- err.span_suggestion_verbose(
- lhs_expr.span.shrink_to_lo(),
- msg,
- "*",
- rustc_errors::Applicability::MachineApplicable,
- );
- }
- }
- };
-
- // We should suggest `a + b` => `*a + b` if `a` is copy, and suggest
- // `a += b` => `*a += b` if a is a mut ref.
- if is_assign == IsAssign::Yes
- && let Some(lhs_deref_ty) = self.deref_once_mutably_for_diagnostic(lhs_ty) {
- suggest_deref_binop(lhs_deref_ty);
- } else if is_assign == IsAssign::No
- && let Ref(_, lhs_deref_ty, _) = lhs_ty.kind() {
- if self.type_is_copy_modulo_regions(self.param_env, *lhs_deref_ty, lhs_expr.span) {
- suggest_deref_binop(*lhs_deref_ty);
- }
- }
- if let Some(missing_trait) = missing_trait {
- let mut visitor = TypeParamVisitor(vec![]);
- visitor.visit_ty(lhs_ty);
-
- if op.node == hir::BinOpKind::Add
- && self.check_str_addition(
- lhs_expr, rhs_expr, lhs_ty, rhs_ty, &mut err, is_assign, op,
- )
- {
- // This has nothing here because it means we did string
- // concatenation (e.g., "Hello " + "World!"). This means
- // we don't want the note in the else clause to be emitted
- } else if let [ty] = &visitor.0[..] {
- // Look for a TraitPredicate in the Fulfillment errors,
- // and use it to generate a suggestion.
- //
- // Note that lookup_op_method must be called again but
- // with a specific rhs_ty instead of a placeholder so
- // the resulting predicate generates a more specific
- // suggestion for the user.
- let errors = self
- .lookup_op_method(
- lhs_ty,
- Some(rhs_ty),
- Some(rhs_expr),
- Op::Binary(op, is_assign),
- expected,
- )
- .unwrap_err();
- if !errors.is_empty() {
- for error in errors {
- if let Some(trait_pred) =
- error.obligation.predicate.to_opt_poly_trait_pred()
- {
- let proj_pred = match error.obligation.cause.code() {
- ObligationCauseCode::BinOp {
- output_pred: Some(output_pred),
- ..
- } if use_output => {
- output_pred.to_opt_poly_projection_pred()
- }
- _ => None,
- };
-
- self.suggest_restricting_param_bound(
- &mut err,
- trait_pred,
- proj_pred,
- self.body_id,
- );
- }
- }
- } else if *ty != lhs_ty {
- // When we know that a missing bound is responsible, we don't show
- // this note as it is redundant.
- err.note(&format!(
- "the trait `{missing_trait}` is not implemented for `{lhs_ty}`"
- ));
- }
- }
- }
- err.emit();
- self.tcx.ty_error()
- }
- };
-
- (lhs_ty, rhs_ty, return_ty)
- }
-
- /// If one of the types is an uncalled function and calling it would yield the other type,
- /// suggest calling the function. Returns `true` if suggestion would apply (even if not given).
- fn add_type_neq_err_label(
- &self,
- err: &mut Diagnostic,
- span: Span,
- ty: Ty<'tcx>,
- other_ty: Ty<'tcx>,
- other_expr: &'tcx hir::Expr<'tcx>,
- op: hir::BinOp,
- is_assign: IsAssign,
- expected: Expectation<'tcx>,
- ) -> bool /* did we suggest to call a function because of missing parentheses? */ {
- err.span_label(span, ty.to_string());
- if let FnDef(def_id, _) = *ty.kind() {
- if !self.tcx.has_typeck_results(def_id) {
- return false;
- }
- // FIXME: Instead of exiting early when encountering bound vars in
- // the function signature, consider keeping the binder here and
- // propagating it downwards.
- let Some(fn_sig) = self.tcx.fn_sig(def_id).no_bound_vars() else {
- return false;
- };
-
- let other_ty = if let FnDef(def_id, _) = *other_ty.kind() {
- if !self.tcx.has_typeck_results(def_id) {
- return false;
- }
- // We're emitting a suggestion, so we can just ignore regions
- self.tcx.fn_sig(def_id).skip_binder().output()
- } else {
- other_ty
- };
-
- if self
- .lookup_op_method(
- fn_sig.output(),
- Some(other_ty),
- Some(other_expr),
- Op::Binary(op, is_assign),
- expected,
- )
- .is_ok()
- {
- let (variable_snippet, applicability) = if !fn_sig.inputs().is_empty() {
- ("( /* arguments */ )", Applicability::HasPlaceholders)
- } else {
- ("()", Applicability::MaybeIncorrect)
- };
-
- err.span_suggestion_verbose(
- span.shrink_to_hi(),
- "you might have forgotten to call this function",
- variable_snippet,
- applicability,
- );
- return true;
- }
- }
- false
- }
-
- /// Provide actionable suggestions when trying to add two strings with incorrect types,
- /// like `&str + &str`, `String + String` and `&str + &String`.
- ///
- /// If this function returns `true` it means a note was printed, so we don't need
- /// to print the normal "implementation of `std::ops::Add` might be missing" note
- fn 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,
- ) -> bool {
- let str_concat_note = "string concatenation requires an owned `String` on the left";
- let rm_borrow_msg = "remove the borrow to obtain an owned `String`";
- let to_owned_msg = "create an owned `String` from a string reference";
-
- let is_std_string = |ty: Ty<'tcx>| {
- ty.ty_adt_def()
- .map_or(false, |ty_def| self.tcx.is_diagnostic_item(sym::String, ty_def.did()))
- };
-
- match (lhs_ty.kind(), rhs_ty.kind()) {
- (&Ref(_, l_ty, _), &Ref(_, r_ty, _)) // &str or &String + &str, &String or &&str
- if (*l_ty.kind() == Str || is_std_string(l_ty))
- && (*r_ty.kind() == Str
- || is_std_string(r_ty)
- || matches!(
- r_ty.kind(), Ref(_, inner_ty, _) if *inner_ty.kind() == Str
- )) =>
- {
- if let IsAssign::No = is_assign { // Do not supply this message if `&str += &str`
- err.span_label(op.span, "`+` cannot be used to concatenate two `&str` strings");
- err.note(str_concat_note);
- if let hir::ExprKind::AddrOf(_, _, lhs_inner_expr) = lhs_expr.kind {
- err.span_suggestion_verbose(
- lhs_expr.span.until(lhs_inner_expr.span),
- rm_borrow_msg,
- "",
- Applicability::MachineApplicable
- );
- } else {
- err.span_suggestion_verbose(
- lhs_expr.span.shrink_to_hi(),
- to_owned_msg,
- ".to_owned()",
- Applicability::MachineApplicable
- );
- }
- }
- true
- }
- (&Ref(_, l_ty, _), &Adt(..)) // Handle `&str` & `&String` + `String`
- if (*l_ty.kind() == Str || is_std_string(l_ty)) && is_std_string(rhs_ty) =>
- {
- err.span_label(
- op.span,
- "`+` cannot be used to concatenate a `&str` with a `String`",
- );
- match is_assign {
- IsAssign::No => {
- let sugg_msg;
- let lhs_sugg = if let hir::ExprKind::AddrOf(_, _, lhs_inner_expr) = lhs_expr.kind {
- sugg_msg = "remove the borrow on the left and add one on the right";
- (lhs_expr.span.until(lhs_inner_expr.span), "".to_owned())
- } else {
- sugg_msg = "create an owned `String` on the left and add a borrow on the right";
- (lhs_expr.span.shrink_to_hi(), ".to_owned()".to_owned())
- };
- let suggestions = vec![
- lhs_sugg,
- (rhs_expr.span.shrink_to_lo(), "&".to_owned()),
- ];
- err.multipart_suggestion_verbose(
- sugg_msg,
- suggestions,
- Applicability::MachineApplicable,
- );
- }
- IsAssign::Yes => {
- err.note(str_concat_note);
- }
- }
- true
- }
- _ => false,
- }
- }
-
- pub fn check_user_unop(
- &self,
- ex: &'tcx hir::Expr<'tcx>,
- operand_ty: Ty<'tcx>,
- op: hir::UnOp,
- expected: Expectation<'tcx>,
- ) -> Ty<'tcx> {
- assert!(op.is_by_value());
- match self.lookup_op_method(operand_ty, None, None, Op::Unary(op, ex.span), expected) {
- Ok(method) => {
- self.write_method_call(ex.hir_id, method);
- method.sig.output()
- }
- Err(errors) => {
- let actual = self.resolve_vars_if_possible(operand_ty);
- if !actual.references_error() {
- let mut err = struct_span_err!(
- self.tcx.sess,
- ex.span,
- E0600,
- "cannot apply unary operator `{}` to type `{}`",
- op.as_str(),
- actual
- );
- err.span_label(
- ex.span,
- format!("cannot apply unary operator `{}`", op.as_str()),
- );
-
- let mut visitor = TypeParamVisitor(vec![]);
- visitor.visit_ty(operand_ty);
- if let [_] = &visitor.0[..] && let ty::Param(_) = *operand_ty.kind() {
- let predicates = errors
- .iter()
- .filter_map(|error| {
- error.obligation.predicate.to_opt_poly_trait_pred()
- });
- for pred in predicates {
- self.suggest_restricting_param_bound(
- &mut err,
- pred,
- None,
- self.body_id,
- );
- }
- }
-
- let sp = self.tcx.sess.source_map().start_point(ex.span);
- if let Some(sp) =
- self.tcx.sess.parse_sess.ambiguous_block_expr_parse.borrow().get(&sp)
- {
- // If the previous expression was a block expression, suggest parentheses
- // (turning this into a binary subtraction operation instead.)
- // for example, `{2} - 2` -> `({2}) - 2` (see src\test\ui\parser\expr-as-stmt.rs)
- self.tcx.sess.parse_sess.expr_parentheses_needed(&mut err, *sp);
- } else {
- match actual.kind() {
- Uint(_) if op == hir::UnOp::Neg => {
- err.note("unsigned values cannot be negated");
-
- if let hir::ExprKind::Unary(
- _,
- hir::Expr {
- kind:
- hir::ExprKind::Lit(Spanned {
- node: ast::LitKind::Int(1, _),
- ..
- }),
- ..
- },
- ) = ex.kind
- {
- err.span_suggestion(
- ex.span,
- &format!(
- "you may have meant the maximum value of `{actual}`",
- ),
- format!("{actual}::MAX"),
- Applicability::MaybeIncorrect,
- );
- }
- }
- Str | Never | Char | Tuple(_) | Array(_, _) => {}
- Ref(_, lty, _) if *lty.kind() == Str => {}
- _ => {
- self.note_unmet_impls_on_type(&mut err, errors);
- }
- }
- }
- err.emit();
- }
- self.tcx.ty_error()
- }
- }
- }
-
- fn lookup_op_method(
- &self,
- lhs_ty: Ty<'tcx>,
- other_ty: Option<Ty<'tcx>>,
- other_ty_expr: Option<&'tcx hir::Expr<'tcx>>,
- op: Op,
- expected: Expectation<'tcx>,
- ) -> Result<MethodCallee<'tcx>, Vec<FulfillmentError<'tcx>>> {
- let lang = self.tcx.lang_items();
-
- let span = match op {
- Op::Binary(op, _) => op.span,
- Op::Unary(_, span) => span,
- };
- let (opname, trait_did) = if let Op::Binary(op, IsAssign::Yes) = op {
- match op.node {
- hir::BinOpKind::Add => (sym::add_assign, lang.add_assign_trait()),
- hir::BinOpKind::Sub => (sym::sub_assign, lang.sub_assign_trait()),
- hir::BinOpKind::Mul => (sym::mul_assign, lang.mul_assign_trait()),
- hir::BinOpKind::Div => (sym::div_assign, lang.div_assign_trait()),
- hir::BinOpKind::Rem => (sym::rem_assign, lang.rem_assign_trait()),
- hir::BinOpKind::BitXor => (sym::bitxor_assign, lang.bitxor_assign_trait()),
- hir::BinOpKind::BitAnd => (sym::bitand_assign, lang.bitand_assign_trait()),
- hir::BinOpKind::BitOr => (sym::bitor_assign, lang.bitor_assign_trait()),
- hir::BinOpKind::Shl => (sym::shl_assign, lang.shl_assign_trait()),
- hir::BinOpKind::Shr => (sym::shr_assign, lang.shr_assign_trait()),
- hir::BinOpKind::Lt
- | hir::BinOpKind::Le
- | hir::BinOpKind::Ge
- | hir::BinOpKind::Gt
- | hir::BinOpKind::Eq
- | hir::BinOpKind::Ne
- | hir::BinOpKind::And
- | hir::BinOpKind::Or => {
- span_bug!(span, "impossible assignment operation: {}=", op.node.as_str())
- }
- }
- } else if let Op::Binary(op, IsAssign::No) = op {
- match op.node {
- hir::BinOpKind::Add => (sym::add, lang.add_trait()),
- hir::BinOpKind::Sub => (sym::sub, lang.sub_trait()),
- hir::BinOpKind::Mul => (sym::mul, lang.mul_trait()),
- hir::BinOpKind::Div => (sym::div, lang.div_trait()),
- hir::BinOpKind::Rem => (sym::rem, lang.rem_trait()),
- hir::BinOpKind::BitXor => (sym::bitxor, lang.bitxor_trait()),
- hir::BinOpKind::BitAnd => (sym::bitand, lang.bitand_trait()),
- hir::BinOpKind::BitOr => (sym::bitor, lang.bitor_trait()),
- hir::BinOpKind::Shl => (sym::shl, lang.shl_trait()),
- hir::BinOpKind::Shr => (sym::shr, lang.shr_trait()),
- hir::BinOpKind::Lt => (sym::lt, lang.partial_ord_trait()),
- hir::BinOpKind::Le => (sym::le, lang.partial_ord_trait()),
- hir::BinOpKind::Ge => (sym::ge, lang.partial_ord_trait()),
- hir::BinOpKind::Gt => (sym::gt, lang.partial_ord_trait()),
- hir::BinOpKind::Eq => (sym::eq, lang.eq_trait()),
- hir::BinOpKind::Ne => (sym::ne, lang.eq_trait()),
- hir::BinOpKind::And | hir::BinOpKind::Or => {
- span_bug!(span, "&& and || are not overloadable")
- }
- }
- } else if let Op::Unary(hir::UnOp::Not, _) = op {
- (sym::not, lang.not_trait())
- } else if let Op::Unary(hir::UnOp::Neg, _) = op {
- (sym::neg, lang.neg_trait())
- } else {
- bug!("lookup_op_method: op not supported: {:?}", op)
- };
-
- debug!(
- "lookup_op_method(lhs_ty={:?}, op={:?}, opname={:?}, trait_did={:?})",
- lhs_ty, op, opname, trait_did
- );
-
- // Catches cases like #83893, where a lang item is declared with the
- // wrong number of generic arguments. Should have yielded an error
- // elsewhere by now, but we have to catch it here so that we do not
- // index `other_tys` out of bounds (if the lang item has too many
- // generic arguments, `other_tys` is too short).
- if !has_expected_num_generic_args(
- self.tcx,
- trait_did,
- match op {
- // Binary ops have a generic right-hand side, unary ops don't
- Op::Binary(..) => 1,
- Op::Unary(..) => 0,
- },
- ) {
- return Err(vec![]);
- }
-
- let opname = Ident::with_dummy_span(opname);
- let method = trait_did.and_then(|trait_did| {
- self.lookup_op_method_in_trait(
- span,
- opname,
- trait_did,
- lhs_ty,
- other_ty,
- other_ty_expr,
- expected,
- )
- });
-
- match (method, trait_did) {
- (Some(ok), _) => {
- let method = self.register_infer_ok_obligations(ok);
- self.select_obligations_where_possible(false, |_| {});
- Ok(method)
- }
- (None, None) => Err(vec![]),
- (None, Some(trait_did)) => {
- let (obligation, _) = self.obligation_for_op_method(
- span,
- trait_did,
- lhs_ty,
- other_ty,
- other_ty_expr,
- expected,
- );
- let mut fulfill = <dyn TraitEngine<'_>>::new(self.tcx);
- fulfill.register_predicate_obligation(self, obligation);
- Err(fulfill.select_where_possible(&self.infcx))
- }
- }
- }
-}
-
-// Binary operator categories. These categories summarize the behavior
-// with respect to the builtin operations supported.
-enum BinOpCategory {
- /// &&, || -- cannot be overridden
- Shortcircuit,
-
- /// <<, >> -- when shifting a single integer, rhs can be any
- /// integer type. For simd, types must match.
- Shift,
-
- /// +, -, etc -- takes equal types, produces same type as input,
- /// applicable to ints/floats/simd
- Math,
-
- /// &, |, ^ -- takes equal types, produces same type as input,
- /// applicable to ints/floats/simd/bool
- Bitwise,
-
- /// ==, !=, etc -- takes equal types, produces bools, except for simd,
- /// which produce the input type
- Comparison,
-}
-
-impl BinOpCategory {
- fn from(op: hir::BinOp) -> BinOpCategory {
- match op.node {
- hir::BinOpKind::Shl | hir::BinOpKind::Shr => BinOpCategory::Shift,
-
- hir::BinOpKind::Add
- | hir::BinOpKind::Sub
- | hir::BinOpKind::Mul
- | hir::BinOpKind::Div
- | hir::BinOpKind::Rem => BinOpCategory::Math,
-
- hir::BinOpKind::BitXor | hir::BinOpKind::BitAnd | hir::BinOpKind::BitOr => {
- BinOpCategory::Bitwise
- }
-
- hir::BinOpKind::Eq
- | hir::BinOpKind::Ne
- | hir::BinOpKind::Lt
- | hir::BinOpKind::Le
- | hir::BinOpKind::Ge
- | hir::BinOpKind::Gt => BinOpCategory::Comparison,
-
- hir::BinOpKind::And | hir::BinOpKind::Or => BinOpCategory::Shortcircuit,
- }
- }
-}
-
-/// Whether the binary operation is an assignment (`a += b`), or not (`a + b`)
-#[derive(Clone, Copy, Debug, PartialEq)]
-enum IsAssign {
- No,
- Yes,
-}
-
-#[derive(Clone, Copy, Debug)]
-enum Op {
- Binary(hir::BinOp, IsAssign),
- Unary(hir::UnOp, Span),
-}
-
-/// Dereferences a single level of immutable referencing.
-fn deref_ty_if_possible<'tcx>(ty: Ty<'tcx>) -> Ty<'tcx> {
- match ty.kind() {
- ty::Ref(_, ty, hir::Mutability::Not) => *ty,
- _ => ty,
- }
-}
-
-/// Returns `true` if this is a built-in arithmetic operation (e.g., u32
-/// + u32, i16x4 == i16x4) and false if these types would have to be
-/// overloaded to be legal. There are two reasons that we distinguish
-/// builtin operations from overloaded ones (vs trying to drive
-/// everything uniformly through the trait system and intrinsics or
-/// something like that):
-///
-/// 1. Builtin operations can trivially be evaluated in constants.
-/// 2. For comparison operators applied to SIMD types the result is
-/// not of type `bool`. For example, `i16x4 == i16x4` yields a
-/// type like `i16x4`. This means that the overloaded trait
-/// `PartialEq` is not applicable.
-///
-/// Reason #2 is the killer. I tried for a while to always use
-/// overloaded logic and just check the types in constants/codegen after
-/// the fact, and it worked fine, except for SIMD types. -nmatsakis
-fn is_builtin_binop<'tcx>(lhs: Ty<'tcx>, rhs: Ty<'tcx>, op: hir::BinOp) -> bool {
- // Special-case a single layer of referencing, so that things like `5.0 + &6.0f32` work.
- // (See https://github.com/rust-lang/rust/issues/57447.)
- let (lhs, rhs) = (deref_ty_if_possible(lhs), deref_ty_if_possible(rhs));
-
- match BinOpCategory::from(op) {
- BinOpCategory::Shortcircuit => true,
-
- BinOpCategory::Shift => {
- lhs.references_error()
- || rhs.references_error()
- || lhs.is_integral() && rhs.is_integral()
- }
-
- BinOpCategory::Math => {
- lhs.references_error()
- || rhs.references_error()
- || lhs.is_integral() && rhs.is_integral()
- || lhs.is_floating_point() && rhs.is_floating_point()
- }
-
- BinOpCategory::Bitwise => {
- lhs.references_error()
- || rhs.references_error()
- || lhs.is_integral() && rhs.is_integral()
- || lhs.is_floating_point() && rhs.is_floating_point()
- || lhs.is_bool() && rhs.is_bool()
- }
-
- BinOpCategory::Comparison => {
- lhs.references_error() || rhs.references_error() || lhs.is_scalar() && rhs.is_scalar()
- }
- }
-}
-
-struct TypeParamVisitor<'tcx>(Vec<Ty<'tcx>>);
-
-impl<'tcx> TypeVisitor<'tcx> for TypeParamVisitor<'tcx> {
- fn visit_ty(&mut self, ty: Ty<'tcx>) -> ControlFlow<Self::BreakTy> {
- if let ty::Param(_) = ty.kind() {
- self.0.push(ty);
- }
- ty.super_visit_with(self)
- }
-}
-
-struct TypeParamEraser<'a, 'tcx>(&'a FnCtxt<'a, 'tcx>, Span);
-
-impl<'tcx> TypeFolder<'tcx> for TypeParamEraser<'_, 'tcx> {
- fn tcx(&self) -> TyCtxt<'tcx> {
- self.0.tcx
- }
-
- fn fold_ty(&mut self, ty: Ty<'tcx>) -> Ty<'tcx> {
- match ty.kind() {
- ty::Param(_) => self.0.next_ty_var(TypeVariableOrigin {
- kind: TypeVariableOriginKind::MiscVariable,
- span: self.1,
- }),
- _ => ty.super_fold_with(self),
- }
- }
-}