Unnamed repository; edit this file 'description' to name the repository.
Diffstat (limited to 'crates/hir-ty/src/infer/expr.rs')
-rw-r--r--crates/hir-ty/src/infer/expr.rs133
1 files changed, 127 insertions, 6 deletions
diff --git a/crates/hir-ty/src/infer/expr.rs b/crates/hir-ty/src/infer/expr.rs
index 3f78806bd7..eda41ba854 100644
--- a/crates/hir-ty/src/infer/expr.rs
+++ b/crates/hir-ty/src/infer/expr.rs
@@ -1071,11 +1071,9 @@ impl<'a> InferenceContext<'a> {
let ret_ty = self.normalize_associated_types_in(ret_ty);
- // use knowledge of built-in binary ops, which can sometimes help inference
- if let Some(builtin_rhs) = self.builtin_binary_op_rhs_expectation(op, lhs_ty.clone()) {
- self.unify(&builtin_rhs, &rhs_ty);
- }
- if let Some(builtin_ret) = self.builtin_binary_op_return_ty(op, lhs_ty, rhs_ty) {
+ if self.is_builtin_binop(&lhs_ty, &rhs_ty, op) {
+ // use knowledge of built-in binary ops, which can sometimes help inference
+ let builtin_ret = self.enforce_builtin_binop_types(&lhs_ty, &rhs_ty, op);
self.unify(&builtin_ret, &ret_ty);
}
@@ -1545,7 +1543,10 @@ impl<'a> InferenceContext<'a> {
fn builtin_binary_op_rhs_expectation(&mut self, op: BinaryOp, lhs_ty: Ty) -> Option<Ty> {
Some(match op {
BinaryOp::LogicOp(..) => TyKind::Scalar(Scalar::Bool).intern(Interner),
- BinaryOp::Assignment { op: None } => lhs_ty,
+ BinaryOp::Assignment { op: None } => {
+ stdx::never!("Simple assignment operator is not binary op.");
+ return None;
+ }
BinaryOp::CmpOp(CmpOp::Eq { .. }) => match self
.resolve_ty_shallow(&lhs_ty)
.kind(Interner)
@@ -1565,6 +1566,126 @@ impl<'a> InferenceContext<'a> {
})
}
+ /// Dereferences a single level of immutable referencing.
+ fn deref_ty_if_possible(&mut self, ty: &Ty) -> Ty {
+ let ty = self.resolve_ty_shallow(ty);
+ match ty.kind(Interner) {
+ TyKind::Ref(Mutability::Not, _, inner) => self.resolve_ty_shallow(inner),
+ _ => ty,
+ }
+ }
+
+ /// Enforces expectations on lhs type and rhs type depending on the operator and returns the
+ /// output type of the binary op.
+ fn enforce_builtin_binop_types(&mut self, lhs: &Ty, rhs: &Ty, op: BinaryOp) -> Ty {
+ // Special-case a single layer of referencing, so that things like `5.0 + &6.0f32` work (See rust-lang/rust#57447).
+ let lhs = self.deref_ty_if_possible(lhs);
+ let rhs = self.deref_ty_if_possible(rhs);
+
+ let (op, is_assign) = match op {
+ BinaryOp::Assignment { op: Some(inner) } => (BinaryOp::ArithOp(inner), true),
+ _ => (op, false),
+ };
+
+ let output_ty = match op {
+ BinaryOp::LogicOp(_) => {
+ let bool_ = self.result.standard_types.bool_.clone();
+ self.unify(&lhs, &bool_);
+ self.unify(&rhs, &bool_);
+ bool_
+ }
+
+ BinaryOp::ArithOp(ArithOp::Shl | ArithOp::Shr) => {
+ // result type is same as LHS always
+ lhs
+ }
+
+ BinaryOp::ArithOp(_) => {
+ // LHS, RHS, and result will have the same type
+ self.unify(&lhs, &rhs);
+ lhs
+ }
+
+ BinaryOp::CmpOp(_) => {
+ // LHS and RHS will have the same type
+ self.unify(&lhs, &rhs);
+ self.result.standard_types.bool_.clone()
+ }
+
+ BinaryOp::Assignment { op: None } => {
+ stdx::never!("Simple assignment operator is not binary op.");
+ lhs
+ }
+
+ BinaryOp::Assignment { .. } => unreachable!("handled above"),
+ };
+
+ if is_assign {
+ self.result.standard_types.unit.clone()
+ } else {
+ output_ty
+ }
+ }
+
+ fn is_builtin_binop(&mut self, lhs: &Ty, rhs: &Ty, op: BinaryOp) -> bool {
+ // Special-case a single layer of referencing, so that things like `5.0 + &6.0f32` work (See rust-lang/rust#57447).
+ let lhs = self.deref_ty_if_possible(lhs);
+ let rhs = self.deref_ty_if_possible(rhs);
+
+ let op = match op {
+ BinaryOp::Assignment { op: Some(inner) } => BinaryOp::ArithOp(inner),
+ _ => op,
+ };
+
+ match op {
+ BinaryOp::LogicOp(_) => true,
+
+ BinaryOp::ArithOp(ArithOp::Shl | ArithOp::Shr) => {
+ lhs.is_integral() && rhs.is_integral()
+ }
+
+ BinaryOp::ArithOp(
+ ArithOp::Add | ArithOp::Sub | ArithOp::Mul | ArithOp::Div | ArithOp::Rem,
+ ) => {
+ lhs.is_integral() && rhs.is_integral()
+ || lhs.is_floating_point() && rhs.is_floating_point()
+ }
+
+ BinaryOp::ArithOp(ArithOp::BitAnd | ArithOp::BitOr | ArithOp::BitXor) => {
+ lhs.is_integral() && rhs.is_integral()
+ || lhs.is_floating_point() && rhs.is_floating_point()
+ || matches!(
+ (lhs.kind(Interner), rhs.kind(Interner)),
+ (TyKind::Scalar(Scalar::Bool), TyKind::Scalar(Scalar::Bool))
+ )
+ }
+
+ BinaryOp::CmpOp(_) => {
+ let is_scalar = |kind| {
+ matches!(
+ kind,
+ &TyKind::Scalar(_)
+ | TyKind::FnDef(..)
+ | TyKind::Function(_)
+ | TyKind::Raw(..)
+ | TyKind::InferenceVar(
+ _,
+ TyVariableKind::Integer | TyVariableKind::Float
+ )
+ )
+ };
+ is_scalar(lhs.kind(Interner)) && is_scalar(rhs.kind(Interner))
+ }
+
+ BinaryOp::Assignment { op: None } => {
+ stdx::never!("Simple assignment operator is not binary op.");
+ false
+ }
+
+ BinaryOp::Assignment { .. } => unreachable!("handled above"),
+ }
+ }
+
fn with_breakable_ctx<T>(
&mut self,
kind: BreakableKind,