Skip to main content

hir_ty/infer/
op.rs

1//! Inference of binary and unary operators.
2
3use std::collections::hash_map;
4
5use hir_def::{FunctionId, GenericParamId, TraitId, hir::ExprId};
6use rustc_ast_ir::Mutability;
7use rustc_type_ir::inherent::{IntoKind, Ty as _};
8use syntax::ast::{ArithOp, BinaryOp, UnaryOp};
9use tracing::debug;
10
11use crate::{
12    Adjust, Adjustment, AutoBorrow,
13    infer::{AllowTwoPhase, AutoBorrowMutability, Expectation, InferenceContext, expr::ExprIsRead},
14    method_resolution::{MethodCallee, TreatNotYetDefinedOpaques},
15    next_solver::{
16        GenericArgs, TraitRef, Ty, TyKind,
17        fulfill::NextSolverError,
18        infer::traits::{Obligation, ObligationCause},
19        obligation_ctxt::ObligationCtxt,
20    },
21};
22
23impl<'db> InferenceContext<'db> {
24    /// Checks a `a <op>= b`
25    pub(crate) fn infer_assign_op_expr(
26        &mut self,
27        expr: ExprId,
28        op: ArithOp,
29        lhs: ExprId,
30        rhs: ExprId,
31    ) -> Ty<'db> {
32        let (lhs_ty, rhs_ty, return_ty) =
33            self.infer_overloaded_binop(expr, lhs, rhs, BinaryOp::Assignment { op: Some(op) });
34
35        let category = BinOpCategory::from(op);
36        let ty = if !lhs_ty.is_ty_var()
37            && !rhs_ty.is_ty_var()
38            && is_builtin_binop(lhs_ty, rhs_ty, category)
39        {
40            self.enforce_builtin_binop_types(expr, lhs_ty, rhs_ty, category);
41            self.types.types.unit
42        } else {
43            return_ty
44        };
45
46        self.check_lhs_assignable(lhs);
47
48        ty
49    }
50
51    /// Checks a potentially overloaded binary operator.
52    pub(crate) fn infer_binop_expr(
53        &mut self,
54        expr: ExprId,
55        op: BinaryOp,
56        lhs_expr: ExprId,
57        rhs_expr: ExprId,
58    ) -> Ty<'db> {
59        debug!(
60            "check_binop(expr.hir_id={:?}, expr={:?}, op={:?}, lhs_expr={:?}, rhs_expr={:?})",
61            expr, expr, op, lhs_expr, rhs_expr
62        );
63
64        match op {
65            BinaryOp::LogicOp(_) => {
66                // && and || are a simple case.
67                self.infer_expr_coerce(
68                    lhs_expr,
69                    &Expectation::HasType(self.types.types.bool),
70                    ExprIsRead::Yes,
71                );
72                let lhs_diverges = self.diverges;
73                self.infer_expr_coerce(
74                    rhs_expr,
75                    &Expectation::HasType(self.types.types.bool),
76                    ExprIsRead::Yes,
77                );
78
79                // Depending on the LHS' value, the RHS can never execute.
80                self.diverges = lhs_diverges;
81
82                self.types.types.bool
83            }
84            _ => {
85                // Otherwise, we always treat operators as if they are
86                // overloaded. This is the way to be most flexible w/r/t
87                // types that get inferred.
88                let (lhs_ty, rhs_ty, return_ty) =
89                    self.infer_overloaded_binop(expr, lhs_expr, rhs_expr, op);
90
91                // Supply type inference hints if relevant. Probably these
92                // hints should be enforced during select as part of the
93                // `consider_unification_despite_ambiguity` routine, but this
94                // more convenient for now.
95                //
96                // The basic idea is to help type inference by taking
97                // advantage of things we know about how the impls for
98                // scalar types are arranged. This is important in a
99                // scenario like `1_u32 << 2`, because it lets us quickly
100                // deduce that the result type should be `u32`, even
101                // though we don't know yet what type 2 has and hence
102                // can't pin this down to a specific impl.
103                let category = BinOpCategory::from(op);
104                if !lhs_ty.is_ty_var()
105                    && !rhs_ty.is_ty_var()
106                    && is_builtin_binop(lhs_ty, rhs_ty, category)
107                {
108                    let builtin_return_ty =
109                        self.enforce_builtin_binop_types(expr, lhs_ty, rhs_ty, category);
110                    _ = self.demand_eqtype(expr.into(), builtin_return_ty, return_ty);
111                    builtin_return_ty
112                } else {
113                    return_ty
114                }
115            }
116        }
117    }
118
119    fn enforce_builtin_binop_types(
120        &mut self,
121        expr: ExprId,
122        lhs_ty: Ty<'db>,
123        rhs_ty: Ty<'db>,
124        category: BinOpCategory,
125    ) -> Ty<'db> {
126        debug_assert!(is_builtin_binop(lhs_ty, rhs_ty, category));
127
128        // Special-case a single layer of referencing, so that things like `5.0 + &6.0f32` work.
129        // (See https://github.com/rust-lang/rust/issues/57447.)
130        let (lhs_ty, rhs_ty) = (deref_ty_if_possible(lhs_ty), deref_ty_if_possible(rhs_ty));
131
132        match category {
133            BinOpCategory::Shortcircuit => {
134                _ = self.demand_suptype(expr.into(), self.types.types.bool, lhs_ty);
135                _ = self.demand_suptype(expr.into(), self.types.types.bool, rhs_ty);
136                self.types.types.bool
137            }
138
139            BinOpCategory::Shift => {
140                // result type is same as LHS always
141                lhs_ty
142            }
143
144            BinOpCategory::Math | BinOpCategory::Bitwise => {
145                // both LHS and RHS and result will have the same type
146                _ = self.demand_suptype(expr.into(), lhs_ty, rhs_ty);
147                lhs_ty
148            }
149
150            BinOpCategory::Comparison => {
151                // both LHS and RHS and result will have the same type
152                _ = self.demand_suptype(expr.into(), lhs_ty, rhs_ty);
153                self.types.types.bool
154            }
155        }
156    }
157
158    fn infer_overloaded_binop(
159        &mut self,
160        expr: ExprId,
161        lhs_expr: ExprId,
162        rhs_expr: ExprId,
163        op: BinaryOp,
164    ) -> (Ty<'db>, Ty<'db>, Ty<'db>) {
165        debug!("infer_overloaded_binop(expr.hir_id={:?}, op={:?})", expr, op);
166
167        let lhs_ty = match op {
168            BinaryOp::Assignment { .. } => {
169                // rust-lang/rust#52126: We have to use strict
170                // equivalence on the LHS of an assign-op like `+=`;
171                // overwritten or mutably-borrowed places cannot be
172                // coerced to a supertype.
173                self.infer_expr_no_expect(lhs_expr, ExprIsRead::Yes)
174            }
175            _ => {
176                // Find a suitable supertype of the LHS expression's type, by coercing to
177                // a type variable, to pass as the `Self` to the trait, avoiding invariant
178                // trait matching creating lifetime constraints that are too strict.
179                // e.g., adding `&'a T` and `&'b T`, given `&'x T: Add<&'x T>`, will result
180                // in `&'a T <: &'x T` and `&'b T <: &'x T`, instead of `'a = 'b = 'x`.
181                let lhs_ty = self.infer_expr_no_expect(lhs_expr, ExprIsRead::Yes);
182                let fresh_var = self.table.next_ty_var(lhs_expr.into());
183                self.demand_coerce(lhs_expr, lhs_ty, fresh_var, AllowTwoPhase::No, ExprIsRead::Yes)
184            }
185        };
186        let lhs_ty = self.table.resolve_vars_with_obligations(lhs_ty);
187
188        // N.B., as we have not yet type-checked the RHS, we don't have the
189        // type at hand. Make a variable to represent it. The whole reason
190        // for this indirection is so that, below, we can check the expr
191        // using this variable as the expected type, which sometimes lets
192        // us do better coercions than we would be able to do otherwise,
193        // particularly for things like `String + &String`.
194        let rhs_ty_var = self.table.next_ty_var(rhs_expr.into());
195        let result = self.lookup_op_method(
196            expr,
197            lhs_ty,
198            Some((rhs_expr, rhs_ty_var)),
199            self.lang_item_for_bin_op(op),
200        );
201
202        // see `NB` above
203        let rhs_ty =
204            self.infer_expr_coerce(rhs_expr, &Expectation::HasType(rhs_ty_var), ExprIsRead::Yes);
205        let rhs_ty = self.table.resolve_vars_with_obligations(rhs_ty);
206
207        let return_ty = match result {
208            Ok(method) => {
209                let by_ref_binop = !is_op_by_value(op);
210                if (matches!(op, BinaryOp::Assignment { .. }) || by_ref_binop)
211                    && let TyKind::Ref(_, _, mutbl) =
212                        method.sig.inputs_and_output.inputs()[0].kind()
213                {
214                    let mutbl = AutoBorrowMutability::new(mutbl, AllowTwoPhase::Yes);
215                    let autoref = Adjustment {
216                        kind: Adjust::Borrow(AutoBorrow::Ref(mutbl)),
217                        target: method.sig.inputs_and_output.inputs()[0].store(),
218                    };
219                    self.write_expr_adj(lhs_expr, Box::new([autoref]));
220                }
221                if by_ref_binop
222                    && let TyKind::Ref(_, _, mutbl) =
223                        method.sig.inputs_and_output.inputs()[1].kind()
224                {
225                    // Allow two-phase borrows for binops in initial deployment
226                    // since they desugar to methods
227                    let mutbl = AutoBorrowMutability::new(mutbl, AllowTwoPhase::Yes);
228
229                    let autoref = Adjustment {
230                        kind: Adjust::Borrow(AutoBorrow::Ref(mutbl)),
231                        target: method.sig.inputs_and_output.inputs()[1].store(),
232                    };
233                    // HACK(eddyb) Bypass checks due to reborrows being in
234                    // some cases applied on the RHS, on top of which we need
235                    // to autoref, which is not allowed by write_expr_adj.
236                    // self.write_expr_adj(rhs_expr, Box::new([autoref]));
237                    match self.result.expr_adjustments.entry(rhs_expr) {
238                        hash_map::Entry::Occupied(mut entry) => {
239                            let mut adjustments = Vec::from(std::mem::take(entry.get_mut()));
240                            adjustments.reserve_exact(1);
241                            adjustments.push(autoref);
242                            entry.insert(adjustments.into_boxed_slice());
243                        }
244                        hash_map::Entry::Vacant(entry) => {
245                            entry.insert(Box::new([autoref]));
246                        }
247                    };
248                }
249                self.write_method_resolution(expr, method.def_id, method.args);
250
251                method.sig.output()
252            }
253            Err(_errors) => {
254                // FIXME: Report diagnostic.
255                self.types.types.error
256            }
257        };
258
259        (lhs_ty, rhs_ty, return_ty)
260    }
261
262    pub(crate) fn infer_user_unop(
263        &mut self,
264        ex: ExprId,
265        operand_ty: Ty<'db>,
266        op: UnaryOp,
267    ) -> Ty<'db> {
268        match self.lookup_op_method(ex, operand_ty, None, self.lang_item_for_unop(op)) {
269            Ok(method) => {
270                self.write_method_resolution(ex, method.def_id, method.args);
271                method.sig.output()
272            }
273            Err(_errors) => {
274                // FIXME: Report diagnostic.
275                self.types.types.error
276            }
277        }
278    }
279
280    fn lookup_op_method(
281        &mut self,
282        expr: ExprId,
283        lhs_ty: Ty<'db>,
284        opt_rhs: Option<(ExprId, Ty<'db>)>,
285        (op_method, trait_did): (Option<FunctionId>, Option<TraitId>),
286    ) -> Result<MethodCallee<'db>, Vec<NextSolverError<'db>>> {
287        let (Some(trait_did), Some(op_method)) = (trait_did, op_method) else {
288            // Bail if the operator trait is not defined.
289            return Err(vec![]);
290        };
291
292        debug!(
293            "lookup_op_method(lhs_ty={:?}, opname={:?}, trait_did={:?})",
294            lhs_ty, op_method, trait_did
295        );
296
297        let opt_rhs_ty = opt_rhs.map(|it| it.1);
298        let cause = ObligationCause::new(expr);
299
300        // We don't consider any other candidates if this lookup fails
301        // so we can freely treat opaque types as inference variables here
302        // to allow more code to compile.
303        let treat_opaques = TreatNotYetDefinedOpaques::AsInfer;
304        let method = self.table.lookup_method_for_operator(
305            cause,
306            trait_did,
307            op_method,
308            lhs_ty,
309            opt_rhs_ty,
310            treat_opaques,
311        );
312        match method {
313            Some(ok) => {
314                let method = self.table.register_infer_ok(ok);
315                self.table.select_obligations_where_possible();
316                Ok(method)
317            }
318            None => {
319                // Guide inference for the RHS expression if it's provided --
320                // this will allow us to better error reporting, at the expense
321                // of making some error messages a bit more specific.
322                if let Some((rhs_expr, rhs_ty)) = opt_rhs
323                    && rhs_ty.is_ty_var()
324                {
325                    self.infer_expr_coerce(
326                        rhs_expr,
327                        &Expectation::HasType(rhs_ty),
328                        ExprIsRead::Yes,
329                    );
330                }
331
332                // Construct an obligation `self_ty : Trait<input_tys>`
333                let args = GenericArgs::for_item(
334                    self.interner(),
335                    trait_did.into(),
336                    |param_idx, param_id, _, _| match param_id {
337                        GenericParamId::LifetimeParamId(_) | GenericParamId::ConstParamId(_) => {
338                            unreachable!("did not expect operand trait to have lifetime/const args")
339                        }
340                        GenericParamId::TypeParamId(_) => {
341                            if param_idx == 0 {
342                                lhs_ty.into()
343                            } else {
344                                opt_rhs_ty.expect("expected RHS for binop").into()
345                            }
346                        }
347                    },
348                );
349                let obligation = Obligation::new(
350                    self.interner(),
351                    cause,
352                    self.table.param_env,
353                    TraitRef::new_from_args(self.interner(), trait_did.into(), args),
354                );
355                let mut ocx = ObligationCtxt::new(self.infcx());
356                ocx.register_obligation(obligation);
357                Err(ocx.evaluate_obligations_error_on_ambiguity())
358            }
359        }
360    }
361
362    fn lang_item_for_bin_op(&self, op: BinaryOp) -> (Option<FunctionId>, Option<TraitId>) {
363        let (method, trait_lang_item) =
364            crate::lang_items::lang_items_for_bin_op(self.lang_items, op)
365                .expect("invalid operator provided");
366        (method, trait_lang_item)
367    }
368
369    fn lang_item_for_unop(&self, op: UnaryOp) -> (Option<FunctionId>, Option<TraitId>) {
370        let (method, trait_lang_item) = match op {
371            UnaryOp::Not => (self.lang_items.Not_not, self.lang_items.Not),
372            UnaryOp::Neg => (self.lang_items.Neg_neg, self.lang_items.Neg),
373            UnaryOp::Deref => panic!("Deref is not overloadable"),
374        };
375        (method, trait_lang_item)
376    }
377}
378
379// Binary operator categories. These categories summarize the behavior
380// with respect to the builtin operations supported.
381#[derive(Clone, Copy)]
382enum BinOpCategory {
383    /// &&, || -- cannot be overridden
384    Shortcircuit,
385
386    /// <<, >> -- when shifting a single integer, rhs can be any
387    /// integer type. For simd, types must match.
388    Shift,
389
390    /// +, -, etc -- takes equal types, produces same type as input,
391    /// applicable to ints/floats/simd
392    Math,
393
394    /// &, |, ^ -- takes equal types, produces same type as input,
395    /// applicable to ints/floats/simd/bool
396    Bitwise,
397
398    /// ==, !=, etc -- takes equal types, produces bools, except for simd,
399    /// which produce the input type
400    Comparison,
401}
402
403impl From<BinaryOp> for BinOpCategory {
404    fn from(op: BinaryOp) -> BinOpCategory {
405        match op {
406            BinaryOp::LogicOp(_) => BinOpCategory::Shortcircuit,
407            BinaryOp::ArithOp(op) | BinaryOp::Assignment { op: Some(op) } => op.into(),
408            BinaryOp::CmpOp(_) => BinOpCategory::Comparison,
409            BinaryOp::Assignment { op: None } => unreachable!(
410                "assignment is lowered into `Expr::Assignment`, not into `Expr::BinaryOp`"
411            ),
412        }
413    }
414}
415
416impl From<ArithOp> for BinOpCategory {
417    fn from(op: ArithOp) -> BinOpCategory {
418        use ArithOp::*;
419        match op {
420            Shl | Shr => BinOpCategory::Shift,
421            Add | Sub | Mul | Div | Rem => BinOpCategory::Math,
422            BitXor | BitAnd | BitOr => BinOpCategory::Bitwise,
423        }
424    }
425}
426
427/// Returns `true` if the binary operator takes its arguments by value.
428fn is_op_by_value(op: BinaryOp) -> bool {
429    !matches!(op, BinaryOp::CmpOp(_))
430}
431
432/// Dereferences a single level of immutable referencing.
433fn deref_ty_if_possible(ty: Ty<'_>) -> Ty<'_> {
434    match ty.kind() {
435        TyKind::Ref(_, ty, Mutability::Not) => ty,
436        _ => ty,
437    }
438}
439
440/// Returns `true` if this is a built-in arithmetic operation (e.g.,
441/// u32 + u32, i16x4 == i16x4) and false if these types would have to be
442/// overloaded to be legal. There are two reasons that we distinguish
443/// builtin operations from overloaded ones (vs trying to drive
444/// everything uniformly through the trait system and intrinsics or
445/// something like that):
446///
447/// 1. Builtin operations can trivially be evaluated in constants.
448/// 2. For comparison operators applied to SIMD types the result is
449///    not of type `bool`. For example, `i16x4 == i16x4` yields a
450///    type like `i16x4`. This means that the overloaded trait
451///    `PartialEq` is not applicable.
452///
453/// Reason #2 is the killer. I tried for a while to always use
454/// overloaded logic and just check the types in constants/codegen after
455/// the fact, and it worked fine, except for SIMD types. -nmatsakis
456fn is_builtin_binop<'db>(lhs: Ty<'db>, rhs: Ty<'db>, category: BinOpCategory) -> bool {
457    // Special-case a single layer of referencing, so that things like `5.0 + &6.0f32` work.
458    // (See https://github.com/rust-lang/rust/issues/57447.)
459    let (lhs, rhs) = (deref_ty_if_possible(lhs), deref_ty_if_possible(rhs));
460
461    match category {
462        BinOpCategory::Shortcircuit => true,
463        BinOpCategory::Shift => lhs.is_integral() && rhs.is_integral(),
464        BinOpCategory::Math => {
465            lhs.is_integral() && rhs.is_integral()
466                || lhs.is_floating_point() && rhs.is_floating_point()
467        }
468        BinOpCategory::Bitwise => {
469            lhs.is_integral() && rhs.is_integral()
470                || lhs.is_floating_point() && rhs.is_floating_point()
471                || lhs.is_bool() && rhs.is_bool()
472        }
473        BinOpCategory::Comparison => lhs.is_scalar() && rhs.is_scalar(),
474    }
475}