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, InferenceDiagnostic,
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                self.push_diagnostic(InferenceDiagnostic::UnaryOperatorCannotBeApplied {
275                    expr: ex,
276                    op,
277                    found: operand_ty.store(),
278                });
279                self.types.types.error
280            }
281        }
282    }
283
284    fn lookup_op_method(
285        &mut self,
286        expr: ExprId,
287        lhs_ty: Ty<'db>,
288        opt_rhs: Option<(ExprId, Ty<'db>)>,
289        (op_method, trait_did): (Option<FunctionId>, Option<TraitId>),
290    ) -> Result<MethodCallee<'db>, Vec<NextSolverError<'db>>> {
291        let (Some(trait_did), Some(op_method)) = (trait_did, op_method) else {
292            // Bail if the operator trait is not defined.
293            return Err(vec![]);
294        };
295
296        debug!(
297            "lookup_op_method(lhs_ty={:?}, opname={:?}, trait_did={:?})",
298            lhs_ty, op_method, trait_did
299        );
300
301        let opt_rhs_ty = opt_rhs.map(|it| it.1);
302        let cause = ObligationCause::new(expr);
303
304        // We don't consider any other candidates if this lookup fails
305        // so we can freely treat opaque types as inference variables here
306        // to allow more code to compile.
307        let treat_opaques = TreatNotYetDefinedOpaques::AsInfer;
308        let method = self.table.lookup_method_for_operator(
309            cause,
310            trait_did,
311            op_method,
312            lhs_ty,
313            opt_rhs_ty,
314            treat_opaques,
315        );
316        match method {
317            Some(ok) => {
318                let method = self.table.register_infer_ok(ok);
319                self.table.select_obligations_where_possible();
320                Ok(method)
321            }
322            None => {
323                // Guide inference for the RHS expression if it's provided --
324                // this will allow us to better error reporting, at the expense
325                // of making some error messages a bit more specific.
326                if let Some((rhs_expr, rhs_ty)) = opt_rhs
327                    && rhs_ty.is_ty_var()
328                {
329                    self.infer_expr_coerce(
330                        rhs_expr,
331                        &Expectation::HasType(rhs_ty),
332                        ExprIsRead::Yes,
333                    );
334                }
335
336                // Construct an obligation `self_ty : Trait<input_tys>`
337                let args = GenericArgs::for_item(
338                    self.interner(),
339                    trait_did.into(),
340                    |param_idx, param_id, _, _| match param_id {
341                        GenericParamId::LifetimeParamId(_) | GenericParamId::ConstParamId(_) => {
342                            unreachable!("did not expect operand trait to have lifetime/const args")
343                        }
344                        GenericParamId::TypeParamId(_) => {
345                            if param_idx == 0 {
346                                lhs_ty.into()
347                            } else {
348                                opt_rhs_ty.expect("expected RHS for binop").into()
349                            }
350                        }
351                    },
352                );
353                let obligation = Obligation::new(
354                    self.interner(),
355                    cause,
356                    self.table.param_env,
357                    TraitRef::new_from_args(self.interner(), trait_did.into(), args),
358                );
359                let mut ocx = ObligationCtxt::new(self.infcx());
360                ocx.register_obligation(obligation);
361                Err(ocx.evaluate_obligations_error_on_ambiguity())
362            }
363        }
364    }
365
366    fn lang_item_for_bin_op(&self, op: BinaryOp) -> (Option<FunctionId>, Option<TraitId>) {
367        let (method, trait_lang_item) =
368            crate::lang_items::lang_items_for_bin_op(self.lang_items, op)
369                .expect("invalid operator provided");
370        (method, trait_lang_item)
371    }
372
373    fn lang_item_for_unop(&self, op: UnaryOp) -> (Option<FunctionId>, Option<TraitId>) {
374        let (method, trait_lang_item) = match op {
375            UnaryOp::Not => (self.lang_items.Not_not, self.lang_items.Not),
376            UnaryOp::Neg => (self.lang_items.Neg_neg, self.lang_items.Neg),
377            UnaryOp::Deref => panic!("Deref is not overloadable"),
378        };
379        (method, trait_lang_item)
380    }
381}
382
383// Binary operator categories. These categories summarize the behavior
384// with respect to the builtin operations supported.
385#[derive(Clone, Copy)]
386enum BinOpCategory {
387    /// &&, || -- cannot be overridden
388    Shortcircuit,
389
390    /// <<, >> -- when shifting a single integer, rhs can be any
391    /// integer type. For simd, types must match.
392    Shift,
393
394    /// +, -, etc -- takes equal types, produces same type as input,
395    /// applicable to ints/floats/simd
396    Math,
397
398    /// &, |, ^ -- takes equal types, produces same type as input,
399    /// applicable to ints/floats/simd/bool
400    Bitwise,
401
402    /// ==, !=, etc -- takes equal types, produces bools, except for simd,
403    /// which produce the input type
404    Comparison,
405}
406
407impl From<BinaryOp> for BinOpCategory {
408    fn from(op: BinaryOp) -> BinOpCategory {
409        match op {
410            BinaryOp::LogicOp(_) => BinOpCategory::Shortcircuit,
411            BinaryOp::ArithOp(op) | BinaryOp::Assignment { op: Some(op) } => op.into(),
412            BinaryOp::CmpOp(_) => BinOpCategory::Comparison,
413            BinaryOp::Assignment { op: None } => unreachable!(
414                "assignment is lowered into `Expr::Assignment`, not into `Expr::BinaryOp`"
415            ),
416        }
417    }
418}
419
420impl From<ArithOp> for BinOpCategory {
421    fn from(op: ArithOp) -> BinOpCategory {
422        use ArithOp::*;
423        match op {
424            Shl | Shr => BinOpCategory::Shift,
425            Add | Sub | Mul | Div | Rem => BinOpCategory::Math,
426            BitXor | BitAnd | BitOr => BinOpCategory::Bitwise,
427        }
428    }
429}
430
431/// Returns `true` if the binary operator takes its arguments by value.
432fn is_op_by_value(op: BinaryOp) -> bool {
433    !matches!(op, BinaryOp::CmpOp(_))
434}
435
436/// Dereferences a single level of immutable referencing.
437fn deref_ty_if_possible(ty: Ty<'_>) -> Ty<'_> {
438    match ty.kind() {
439        TyKind::Ref(_, ty, Mutability::Not) => ty,
440        _ => ty,
441    }
442}
443
444/// Returns `true` if this is a built-in arithmetic operation (e.g.,
445/// u32 + u32, i16x4 == i16x4) and false if these types would have to be
446/// overloaded to be legal. There are two reasons that we distinguish
447/// builtin operations from overloaded ones (vs trying to drive
448/// everything uniformly through the trait system and intrinsics or
449/// something like that):
450///
451/// 1. Builtin operations can trivially be evaluated in constants.
452/// 2. For comparison operators applied to SIMD types the result is
453///    not of type `bool`. For example, `i16x4 == i16x4` yields a
454///    type like `i16x4`. This means that the overloaded trait
455///    `PartialEq` is not applicable.
456///
457/// Reason #2 is the killer. I tried for a while to always use
458/// overloaded logic and just check the types in constants/codegen after
459/// the fact, and it worked fine, except for SIMD types. -nmatsakis
460fn is_builtin_binop<'db>(lhs: Ty<'db>, rhs: Ty<'db>, category: BinOpCategory) -> bool {
461    // Special-case a single layer of referencing, so that things like `5.0 + &6.0f32` work.
462    // (See https://github.com/rust-lang/rust/issues/57447.)
463    let (lhs, rhs) = (deref_ty_if_possible(lhs), deref_ty_if_possible(rhs));
464
465    match category {
466        BinOpCategory::Shortcircuit => true,
467        BinOpCategory::Shift => lhs.is_integral() && rhs.is_integral(),
468        BinOpCategory::Math => {
469            lhs.is_integral() && rhs.is_integral()
470                || lhs.is_floating_point() && rhs.is_floating_point()
471        }
472        BinOpCategory::Bitwise => {
473            lhs.is_integral() && rhs.is_integral()
474                || lhs.is_floating_point() && rhs.is_floating_point()
475                || lhs.is_bool() && rhs.is_bool()
476        }
477        BinOpCategory::Comparison => lhs.is_scalar() && rhs.is_scalar(),
478    }
479}