1use 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 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 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 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 self.diverges = lhs_diverges;
81
82 self.types.types.bool
83 }
84 _ => {
85 let (lhs_ty, rhs_ty, return_ty) =
89 self.infer_overloaded_binop(expr, lhs_expr, rhs_expr, op);
90
91 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 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 lhs_ty
142 }
143
144 BinOpCategory::Math | BinOpCategory::Bitwise => {
145 _ = self.demand_suptype(expr.into(), lhs_ty, rhs_ty);
147 lhs_ty
148 }
149
150 BinOpCategory::Comparison => {
151 _ = 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 self.infer_expr_no_expect(lhs_expr, ExprIsRead::Yes)
174 }
175 _ => {
176 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 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 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 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 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 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.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 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 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 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 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#[derive(Clone, Copy)]
382enum BinOpCategory {
383 Shortcircuit,
385
386 Shift,
389
390 Math,
393
394 Bitwise,
397
398 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
427fn is_op_by_value(op: BinaryOp) -> bool {
429 !matches!(op, BinaryOp::CmpOp(_))
430}
431
432fn deref_ty_if_possible(ty: Ty<'_>) -> Ty<'_> {
434 match ty.kind() {
435 TyKind::Ref(_, ty, Mutability::Not) => ty,
436 _ => ty,
437 }
438}
439
440fn is_builtin_binop<'db>(lhs: Ty<'db>, rhs: Ty<'db>, category: BinOpCategory) -> bool {
457 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}