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.rs | 133 |
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, |