Enforce builtin binop expectations on single references

Also don't enforce them on non-builtin types
This commit is contained in:
Ryo Yoshida 2023-01-17 19:48:25 +09:00
parent fa874627f0
commit 461435adab
No known key found for this signature in database
GPG key ID: E25698A930586171
3 changed files with 243 additions and 33 deletions

View file

@ -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,