mirror of
https://github.com/rust-lang/rust-analyzer.git
synced 2025-09-27 04:19:13 +00:00
Enforce builtin binop expectations on single references
Also don't enforce them on non-builtin types
This commit is contained in:
parent
fa874627f0
commit
461435adab
3 changed files with 243 additions and 33 deletions
|
@ -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,
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue