Merge #9988

9988: fix: Refactor & improve handling of overloaded binary operators r=flodiebold a=flodiebold

Fixes #9971. Also records them as method resolutions, which we could use later.

Co-authored-by: Florian Diebold <flodiebold@gmail.com>
diff --git a/crates/hir_def/src/data.rs b/crates/hir_def/src/data.rs
index 985aa56..c63e931 100644
--- a/crates/hir_def/src/data.rs
+++ b/crates/hir_def/src/data.rs
@@ -199,6 +199,13 @@
             _ => None,
         })
     }
+
+    pub fn method_by_name(&self, name: &Name) -> Option<FunctionId> {
+        self.items.iter().find_map(|(item_name, item)| match item {
+            AssocItemId::FunctionId(t) if item_name == name => Some(*t),
+            _ => None,
+        })
+    }
 }
 
 #[derive(Debug, Clone, PartialEq, Eq)]
diff --git a/crates/hir_expand/src/name.rs b/crates/hir_expand/src/name.rs
index b7b0f0b..4750d65 100644
--- a/crates/hir_expand/src/name.rs
+++ b/crates/hir_expand/src/name.rs
@@ -285,6 +285,33 @@
         wrapping_add,
         wrapping_mul,
         wrapping_sub,
+        // known methods of lang items
+        add,
+        mul,
+        sub,
+        div,
+        rem,
+        shl,
+        shr,
+        bitxor,
+        bitor,
+        bitand,
+        add_assign,
+        mul_assign,
+        sub_assign,
+        div_assign,
+        rem_assign,
+        shl_assign,
+        shr_assign,
+        bitxor_assign,
+        bitor_assign,
+        bitand_assign,
+        eq,
+        ne,
+        ge,
+        gt,
+        le,
+        lt,
     );
 
     // self/Self cannot be used as an identifier
diff --git a/crates/hir_ty/src/infer.rs b/crates/hir_ty/src/infer.rs
index 7154666..d07a939 100644
--- a/crates/hir_ty/src/infer.rs
+++ b/crates/hir_ty/src/infer.rs
@@ -16,11 +16,11 @@
 use std::ops::Index;
 use std::sync::Arc;
 
-use chalk_ir::{cast::Cast, DebruijnIndex, Mutability, Safety};
+use chalk_ir::{cast::Cast, DebruijnIndex, Mutability, Safety, Scalar};
 use hir_def::{
     body::Body,
     data::{ConstData, FunctionData, StaticData},
-    expr::{ArithOp, BinaryOp, BindingAnnotation, ExprId, PatId},
+    expr::{BindingAnnotation, ExprId, PatId},
     lang_item::LangItemTarget,
     path::{path, Path},
     resolver::{HasResolver, ResolveValueResult, Resolver, TypeNs, ValueNs},
@@ -134,11 +134,17 @@
 #[derive(Clone, PartialEq, Eq, Debug)]
 struct InternedStandardTypes {
     unknown: Ty,
+    bool_: Ty,
+    unit: Ty,
 }
 
 impl Default for InternedStandardTypes {
     fn default() -> Self {
-        InternedStandardTypes { unknown: TyKind::Error.intern(&Interner) }
+        InternedStandardTypes {
+            unknown: TyKind::Error.intern(&Interner),
+            bool_: TyKind::Scalar(Scalar::Bool).intern(&Interner),
+            unit: TyKind::Tuple(0, Substitution::empty(&Interner)).intern(&Interner),
+        }
     }
 }
 /// Represents coercing a value to a different type of value.
@@ -751,28 +757,6 @@
         self.db.trait_data(trait_).associated_type_by_name(&name![Output])
     }
 
-    fn resolve_binary_op_output(&self, bop: &BinaryOp) -> Option<TypeAliasId> {
-        let lang_item = match bop {
-            BinaryOp::ArithOp(aop) => match aop {
-                ArithOp::Add => "add",
-                ArithOp::Sub => "sub",
-                ArithOp::Mul => "mul",
-                ArithOp::Div => "div",
-                ArithOp::Shl => "shl",
-                ArithOp::Shr => "shr",
-                ArithOp::Rem => "rem",
-                ArithOp::BitXor => "bitxor",
-                ArithOp::BitOr => "bitor",
-                ArithOp::BitAnd => "bitand",
-            },
-            _ => return None,
-        };
-
-        let trait_ = self.resolve_lang_item(lang_item)?.as_trait();
-
-        self.db.trait_data(trait_?).associated_type_by_name(&name![Output])
-    }
-
     fn resolve_boxed_box(&self) -> Option<AdtId> {
         let struct_ = self.resolve_lang_item("owned_box")?.as_struct()?;
         Some(struct_.into())
@@ -846,6 +830,10 @@
         }
     }
 
+    fn from_option(ty: Option<Ty>) -> Self {
+        ty.map_or(Expectation::None, Expectation::HasType)
+    }
+
     /// The following explanation is copied straight from rustc:
     /// Provides an expectation for an rvalue expression given an *optional*
     /// hint, which is not required for type safety (the resulting type might
diff --git a/crates/hir_ty/src/infer/expr.rs b/crates/hir_ty/src/infer/expr.rs
index 881862e..ceb5eee 100644
--- a/crates/hir_ty/src/infer/expr.rs
+++ b/crates/hir_ty/src/infer/expr.rs
@@ -8,10 +8,13 @@
 
 use chalk_ir::{cast::Cast, fold::Shift, Mutability, TyVariableKind};
 use hir_def::{
-    expr::{Array, BinaryOp, Expr, ExprId, Literal, MatchGuard, Statement, UnaryOp},
+    expr::{
+        ArithOp, Array, BinaryOp, CmpOp, Expr, ExprId, Literal, MatchGuard, Ordering, Statement,
+        UnaryOp,
+    },
     path::{GenericArg, GenericArgs},
     resolver::resolver_for_expr,
-    AssocContainerId, FieldId, Lookup,
+    AssocContainerId, FieldId, FunctionId, Lookup,
 };
 use hir_expand::name::{name, Name};
 use stdx::always;
@@ -23,7 +26,7 @@
     infer::coerce::CoerceMany,
     lower::lower_to_chalk_mutability,
     mapping::from_chalk,
-    method_resolution, op,
+    method_resolution,
     primitive::{self, UintTy},
     static_lifetime, to_chalk_trait_id,
     traits::FnTrait,
@@ -669,34 +672,21 @@
                 }
             }
             Expr::BinaryOp { lhs, rhs, op } => match op {
-                Some(op) => {
-                    let lhs_expectation = match op {
-                        BinaryOp::LogicOp(..) => {
-                            Expectation::has_type(TyKind::Scalar(Scalar::Bool).intern(&Interner))
-                        }
-                        _ => Expectation::none(),
-                    };
-                    let lhs_ty = self.infer_expr(*lhs, &lhs_expectation);
-                    let lhs_ty = self.resolve_ty_shallow(&lhs_ty);
-                    let rhs_expectation = op::binary_op_rhs_expectation(*op, lhs_ty.clone());
-                    let rhs_ty =
-                        self.infer_expr_coerce(*rhs, &Expectation::has_type(rhs_expectation));
-                    let rhs_ty = self.resolve_ty_shallow(&rhs_ty);
-
-                    let ret = op::binary_op_return_ty(*op, lhs_ty.clone(), rhs_ty.clone());
-
-                    if ret.is_unknown() {
-                        cov_mark::hit!(infer_expr_inner_binary_operator_overload);
-
-                        self.resolve_associated_type_with_params(
-                            lhs_ty,
-                            self.resolve_binary_op_output(op),
-                            &[rhs_ty],
-                        )
-                    } else {
-                        ret
-                    }
+                Some(BinaryOp::Assignment { op: None }) => {
+                    let lhs_ty = self.infer_expr(*lhs, &Expectation::none());
+                    self.infer_expr_coerce(*rhs, &Expectation::has_type(lhs_ty));
+                    self.result.standard_types.unit.clone()
                 }
+                Some(BinaryOp::LogicOp(_)) => {
+                    let bool_ty = self.result.standard_types.bool_.clone();
+                    self.infer_expr_coerce(*lhs, &Expectation::HasType(bool_ty.clone()));
+                    let lhs_diverges = self.diverges;
+                    self.infer_expr_coerce(*rhs, &Expectation::HasType(bool_ty.clone()));
+                    // Depending on the LHS' value, the RHS can never execute.
+                    self.diverges = lhs_diverges;
+                    bool_ty
+                }
+                Some(op) => self.infer_overloadable_binop(*lhs, *op, *rhs, tgt_expr),
                 _ => self.err_ty(),
             },
             Expr::Range { lhs, rhs, range_type } => {
@@ -862,6 +852,62 @@
         ty
     }
 
+    fn infer_overloadable_binop(
+        &mut self,
+        lhs: ExprId,
+        op: BinaryOp,
+        rhs: ExprId,
+        tgt_expr: ExprId,
+    ) -> Ty {
+        let lhs_expectation = Expectation::none();
+        let lhs_ty = self.infer_expr(lhs, &lhs_expectation);
+        let rhs_ty = self.table.new_type_var();
+
+        let func = self.resolve_binop_method(op);
+        let func = match func {
+            Some(func) => func,
+            None => {
+                let rhs_ty = self.builtin_binary_op_rhs_expectation(op, lhs_ty.clone());
+                let rhs_ty = self.infer_expr_coerce(rhs, &Expectation::from_option(rhs_ty));
+                return self
+                    .builtin_binary_op_return_ty(op, lhs_ty, rhs_ty)
+                    .unwrap_or_else(|| self.err_ty());
+            }
+        };
+
+        let subst = TyBuilder::subst_for_def(self.db, func)
+            .push(lhs_ty.clone())
+            .push(rhs_ty.clone())
+            .build();
+        self.write_method_resolution(tgt_expr, func, subst.clone());
+
+        let method_ty = self.db.value_ty(func.into()).substitute(&Interner, &subst);
+        self.register_obligations_for_call(&method_ty);
+
+        self.infer_expr_coerce(rhs, &Expectation::has_type(rhs_ty.clone()));
+
+        let ret_ty = match method_ty.callable_sig(self.db) {
+            Some(sig) => sig.ret().clone(),
+            None => self.err_ty(),
+        };
+
+        let ret_ty = self.normalize_associated_types_in(ret_ty);
+
+        // FIXME: record autoref adjustments
+
+        // 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.clone(), rhs_ty.clone())
+        {
+            self.unify(&builtin_ret, &ret_ty);
+        }
+
+        ret_ty
+    }
+
     fn infer_block(
         &mut self,
         expr: ExprId,
@@ -1136,4 +1182,141 @@
             }
         }
     }
+
+    fn builtin_binary_op_return_ty(&mut self, op: BinaryOp, lhs_ty: Ty, rhs_ty: Ty) -> Option<Ty> {
+        let lhs_ty = self.resolve_ty_shallow(&lhs_ty);
+        let rhs_ty = self.resolve_ty_shallow(&rhs_ty);
+        match op {
+            BinaryOp::LogicOp(_) | BinaryOp::CmpOp(_) => {
+                Some(TyKind::Scalar(Scalar::Bool).intern(&Interner))
+            }
+            BinaryOp::Assignment { .. } => Some(TyBuilder::unit()),
+            BinaryOp::ArithOp(ArithOp::Shl | ArithOp::Shr) => {
+                // all integer combinations are valid here
+                if matches!(
+                    lhs_ty.kind(&Interner),
+                    TyKind::Scalar(Scalar::Int(_) | Scalar::Uint(_))
+                        | TyKind::InferenceVar(_, TyVariableKind::Integer)
+                ) && matches!(
+                    rhs_ty.kind(&Interner),
+                    TyKind::Scalar(Scalar::Int(_) | Scalar::Uint(_))
+                        | TyKind::InferenceVar(_, TyVariableKind::Integer)
+                ) {
+                    Some(lhs_ty)
+                } else {
+                    None
+                }
+            }
+            BinaryOp::ArithOp(_) => match (lhs_ty.kind(&Interner), rhs_ty.kind(&Interner)) {
+                // (int, int) | (uint, uint) | (float, float)
+                (TyKind::Scalar(Scalar::Int(_)), TyKind::Scalar(Scalar::Int(_)))
+                | (TyKind::Scalar(Scalar::Uint(_)), TyKind::Scalar(Scalar::Uint(_)))
+                | (TyKind::Scalar(Scalar::Float(_)), TyKind::Scalar(Scalar::Float(_))) => {
+                    Some(rhs_ty)
+                }
+                // ({int}, int) | ({int}, uint)
+                (
+                    TyKind::InferenceVar(_, TyVariableKind::Integer),
+                    TyKind::Scalar(Scalar::Int(_) | Scalar::Uint(_)),
+                ) => Some(rhs_ty),
+                // (int, {int}) | (uint, {int})
+                (
+                    TyKind::Scalar(Scalar::Int(_) | Scalar::Uint(_)),
+                    TyKind::InferenceVar(_, TyVariableKind::Integer),
+                ) => Some(lhs_ty),
+                // ({float} | float)
+                (
+                    TyKind::InferenceVar(_, TyVariableKind::Float),
+                    TyKind::Scalar(Scalar::Float(_)),
+                ) => Some(rhs_ty),
+                // (float, {float})
+                (
+                    TyKind::Scalar(Scalar::Float(_)),
+                    TyKind::InferenceVar(_, TyVariableKind::Float),
+                ) => Some(lhs_ty),
+                // ({int}, {int}) | ({float}, {float})
+                (
+                    TyKind::InferenceVar(_, TyVariableKind::Integer),
+                    TyKind::InferenceVar(_, TyVariableKind::Integer),
+                )
+                | (
+                    TyKind::InferenceVar(_, TyVariableKind::Float),
+                    TyKind::InferenceVar(_, TyVariableKind::Float),
+                ) => Some(rhs_ty),
+                _ => None,
+            },
+        }
+    }
+
+    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::CmpOp(CmpOp::Eq { .. }) => match self
+                .resolve_ty_shallow(&lhs_ty)
+                .kind(&Interner)
+            {
+                TyKind::Scalar(_) | TyKind::Str => lhs_ty,
+                TyKind::InferenceVar(_, TyVariableKind::Integer | TyVariableKind::Float) => lhs_ty,
+                _ => return None,
+            },
+            BinaryOp::ArithOp(ArithOp::Shl | ArithOp::Shr) => return None,
+            BinaryOp::CmpOp(CmpOp::Ord { .. })
+            | BinaryOp::Assignment { op: Some(_) }
+            | BinaryOp::ArithOp(_) => match self.resolve_ty_shallow(&lhs_ty).kind(&Interner) {
+                TyKind::Scalar(Scalar::Int(_) | Scalar::Uint(_) | Scalar::Float(_)) => lhs_ty,
+                TyKind::InferenceVar(_, TyVariableKind::Integer | TyVariableKind::Float) => lhs_ty,
+                _ => return None,
+            },
+        })
+    }
+
+    fn resolve_binop_method(&self, op: BinaryOp) -> Option<FunctionId> {
+        let (name, lang_item) = match op {
+            BinaryOp::LogicOp(_) => return None,
+            BinaryOp::ArithOp(aop) => match aop {
+                ArithOp::Add => (name!(add), "add"),
+                ArithOp::Mul => (name!(mul), "mul"),
+                ArithOp::Sub => (name!(sub), "sub"),
+                ArithOp::Div => (name!(div), "div"),
+                ArithOp::Rem => (name!(rem), "rem"),
+                ArithOp::Shl => (name!(shl), "shl"),
+                ArithOp::Shr => (name!(shr), "shr"),
+                ArithOp::BitXor => (name!(bitxor), "bitxor"),
+                ArithOp::BitOr => (name!(bitor), "bitor"),
+                ArithOp::BitAnd => (name!(bitand), "bitand"),
+            },
+            BinaryOp::Assignment { op: Some(aop) } => match aop {
+                ArithOp::Add => (name!(add_assign), "add_assign"),
+                ArithOp::Mul => (name!(mul_assign), "mul_assign"),
+                ArithOp::Sub => (name!(sub_assign), "sub_assign"),
+                ArithOp::Div => (name!(div_assign), "div_assign"),
+                ArithOp::Rem => (name!(rem_assign), "rem_assign"),
+                ArithOp::Shl => (name!(shl_assign), "shl_assign"),
+                ArithOp::Shr => (name!(shr_assign), "shr_assign"),
+                ArithOp::BitXor => (name!(bitxor_assign), "bitxor_assign"),
+                ArithOp::BitOr => (name!(bitor_assign), "bitor_assign"),
+                ArithOp::BitAnd => (name!(bitand_assign), "bitand_assign"),
+            },
+            BinaryOp::CmpOp(cop) => match cop {
+                CmpOp::Eq { negated: false } => (name!(eq), "eq"),
+                CmpOp::Eq { negated: true } => (name!(ne), "eq"),
+                CmpOp::Ord { ordering: Ordering::Less, strict: false } => {
+                    (name!(le), "partial_ord")
+                }
+                CmpOp::Ord { ordering: Ordering::Less, strict: true } => (name!(lt), "partial_ord"),
+                CmpOp::Ord { ordering: Ordering::Greater, strict: false } => {
+                    (name!(ge), "partial_ord")
+                }
+                CmpOp::Ord { ordering: Ordering::Greater, strict: true } => {
+                    (name!(gt), "partial_ord")
+                }
+            },
+            BinaryOp::Assignment { op: None } => return None,
+        };
+
+        let trait_ = self.resolve_lang_item(lang_item)?.as_trait()?;
+
+        self.db.trait_data(trait_).method_by_name(&name)
+    }
 }
diff --git a/crates/hir_ty/src/lib.rs b/crates/hir_ty/src/lib.rs
index 2026a42..f59e109 100644
--- a/crates/hir_ty/src/lib.rs
+++ b/crates/hir_ty/src/lib.rs
@@ -15,7 +15,6 @@
 mod interner;
 mod lower;
 mod mapping;
-mod op;
 mod tls;
 mod utils;
 mod walk;
diff --git a/crates/hir_ty/src/op.rs b/crates/hir_ty/src/op.rs
deleted file mode 100644
index 5ef6342..0000000
--- a/crates/hir_ty/src/op.rs
+++ /dev/null
@@ -1,82 +0,0 @@
-//! Helper functions for binary operator type inference.
-use chalk_ir::TyVariableKind;
-use hir_def::expr::{ArithOp, BinaryOp, CmpOp};
-
-use crate::{Interner, Scalar, Ty, TyBuilder, TyKind};
-
-pub(super) fn binary_op_return_ty(op: BinaryOp, lhs_ty: Ty, rhs_ty: Ty) -> Ty {
-    match op {
-        BinaryOp::LogicOp(_) | BinaryOp::CmpOp(_) => TyKind::Scalar(Scalar::Bool).intern(&Interner),
-        BinaryOp::Assignment { .. } => TyBuilder::unit(),
-        BinaryOp::ArithOp(ArithOp::Shl | ArithOp::Shr) => {
-            // all integer combinations are valid here
-            if matches!(
-                lhs_ty.kind(&Interner),
-                TyKind::Scalar(Scalar::Int(_) | Scalar::Uint(_))
-                    | TyKind::InferenceVar(_, TyVariableKind::Integer)
-            ) && matches!(
-                rhs_ty.kind(&Interner),
-                TyKind::Scalar(Scalar::Int(_) | Scalar::Uint(_))
-                    | TyKind::InferenceVar(_, TyVariableKind::Integer)
-            ) {
-                lhs_ty
-            } else {
-                TyKind::Error.intern(&Interner)
-            }
-        }
-        BinaryOp::ArithOp(_) => match (lhs_ty.kind(&Interner), rhs_ty.kind(&Interner)) {
-            // (int, int) | (uint, uint) | (float, float)
-            (TyKind::Scalar(Scalar::Int(_)), TyKind::Scalar(Scalar::Int(_)))
-            | (TyKind::Scalar(Scalar::Uint(_)), TyKind::Scalar(Scalar::Uint(_)))
-            | (TyKind::Scalar(Scalar::Float(_)), TyKind::Scalar(Scalar::Float(_))) => rhs_ty,
-            // ({int}, int) | ({int}, uint)
-            (
-                TyKind::InferenceVar(_, TyVariableKind::Integer),
-                TyKind::Scalar(Scalar::Int(_) | Scalar::Uint(_)),
-            ) => rhs_ty,
-            // (int, {int}) | (uint, {int})
-            (
-                TyKind::Scalar(Scalar::Int(_) | Scalar::Uint(_)),
-                TyKind::InferenceVar(_, TyVariableKind::Integer),
-            ) => lhs_ty,
-            // ({float} | float)
-            (TyKind::InferenceVar(_, TyVariableKind::Float), TyKind::Scalar(Scalar::Float(_))) => {
-                rhs_ty
-            }
-            // (float, {float})
-            (TyKind::Scalar(Scalar::Float(_)), TyKind::InferenceVar(_, TyVariableKind::Float)) => {
-                lhs_ty
-            }
-            // ({int}, {int}) | ({float}, {float})
-            (
-                TyKind::InferenceVar(_, TyVariableKind::Integer),
-                TyKind::InferenceVar(_, TyVariableKind::Integer),
-            )
-            | (
-                TyKind::InferenceVar(_, TyVariableKind::Float),
-                TyKind::InferenceVar(_, TyVariableKind::Float),
-            ) => rhs_ty,
-            _ => TyKind::Error.intern(&Interner),
-        },
-    }
-}
-
-pub(super) fn binary_op_rhs_expectation(op: BinaryOp, lhs_ty: Ty) -> Ty {
-    match op {
-        BinaryOp::LogicOp(..) => TyKind::Scalar(Scalar::Bool).intern(&Interner),
-        BinaryOp::Assignment { op: None } => lhs_ty,
-        BinaryOp::CmpOp(CmpOp::Eq { .. }) => match lhs_ty.kind(&Interner) {
-            TyKind::Scalar(_) | TyKind::Str => lhs_ty,
-            TyKind::InferenceVar(_, TyVariableKind::Integer | TyVariableKind::Float) => lhs_ty,
-            _ => TyKind::Error.intern(&Interner),
-        },
-        BinaryOp::ArithOp(ArithOp::Shl | ArithOp::Shr) => TyKind::Error.intern(&Interner),
-        BinaryOp::CmpOp(CmpOp::Ord { .. })
-        | BinaryOp::Assignment { op: Some(_) }
-        | BinaryOp::ArithOp(_) => match lhs_ty.kind(&Interner) {
-            TyKind::Scalar(Scalar::Int(_) | Scalar::Uint(_) | Scalar::Float(_)) => lhs_ty,
-            TyKind::InferenceVar(_, TyVariableKind::Integer | TyVariableKind::Float) => lhs_ty,
-            _ => TyKind::Error.intern(&Interner),
-        },
-    }
-}
diff --git a/crates/hir_ty/src/tests/coercion.rs b/crates/hir_ty/src/tests/coercion.rs
index bcec109..dd3b86f 100644
--- a/crates/hir_ty/src/tests/coercion.rs
+++ b/crates/hir_ty/src/tests/coercion.rs
@@ -630,3 +630,27 @@
 "#,
     )
 }
+
+#[test]
+fn coerce_overloaded_binary_op_rhs() {
+    check_types(
+        r#"
+//- minicore: deref, add
+
+struct String {}
+impl core::ops::Deref for String { type Target = str; }
+
+impl core::ops::Add<&str> for String {
+    type Output = String;
+}
+
+fn test() {
+    let s1 = String {};
+    let s2 = String {};
+    s1 + &s2;
+  //^^^^^^^^ String
+}
+
+        "#,
+    );
+}
diff --git a/crates/hir_ty/src/tests/simple.rs b/crates/hir_ty/src/tests/simple.rs
index b4bcc6d..a409c45 100644
--- a/crates/hir_ty/src/tests/simple.rs
+++ b/crates/hir_ty/src/tests/simple.rs
@@ -2311,89 +2311,24 @@
 
 #[test]
 fn infer_operator_overload() {
-    cov_mark::check!(infer_expr_inner_binary_operator_overload);
-
-    check_infer(
+    check_types(
         r#"
-        struct V2([f32; 2]);
+//- minicore: add
+struct V2([f32; 2]);
 
-        #[lang = "add"]
-        pub trait Add<Rhs = Self> {
-            /// The resulting type after applying the `+` operator.
-            type Output;
+impl core::ops::Add<V2> for V2 {
+    type Output = V2;
+}
 
-            /// Performs the `+` operation.
-            #[must_use]
-            fn add(self, rhs: Rhs) -> Self::Output;
-        }
+fn test() {
+    let va = V2([0.0, 1.0]);
+    let vb = V2([0.0, 1.0]);
 
-        impl Add<V2> for V2 {
-            type Output = V2;
-
-            fn add(self, rhs: V2) -> V2 {
-                let x = self.0[0] + rhs.0[0];
-                let y = self.0[1] + rhs.0[1];
-                V2([x, y])
-            }
-        }
-
-        fn test() {
-            let va = V2([0.0, 1.0]);
-            let vb = V2([0.0, 1.0]);
-
-            let r = va + vb;
-        }
+    let r = va + vb;
+    //      ^^^^^^^ V2
+}
 
         "#,
-        expect![[r#"
-            207..211 'self': Self
-            213..216 'rhs': Rhs
-            299..303 'self': V2
-            305..308 'rhs': V2
-            320..422 '{     ...     }': V2
-            334..335 'x': f32
-            338..342 'self': V2
-            338..344 'self.0': [f32; 2]
-            338..347 'self.0[0]': {unknown}
-            338..358 'self.0...s.0[0]': f32
-            345..346 '0': i32
-            350..353 'rhs': V2
-            350..355 'rhs.0': [f32; 2]
-            350..358 'rhs.0[0]': {unknown}
-            356..357 '0': i32
-            372..373 'y': f32
-            376..380 'self': V2
-            376..382 'self.0': [f32; 2]
-            376..385 'self.0[1]': {unknown}
-            376..396 'self.0...s.0[1]': f32
-            383..384 '1': i32
-            388..391 'rhs': V2
-            388..393 'rhs.0': [f32; 2]
-            388..396 'rhs.0[1]': {unknown}
-            394..395 '1': i32
-            406..408 'V2': V2([f32; 2]) -> V2
-            406..416 'V2([x, y])': V2
-            409..415 '[x, y]': [f32; 2]
-            410..411 'x': f32
-            413..414 'y': f32
-            436..519 '{     ... vb; }': ()
-            446..448 'va': V2
-            451..453 'V2': V2([f32; 2]) -> V2
-            451..465 'V2([0.0, 1.0])': V2
-            454..464 '[0.0, 1.0]': [f32; 2]
-            455..458 '0.0': f32
-            460..463 '1.0': f32
-            475..477 'vb': V2
-            480..482 'V2': V2([f32; 2]) -> V2
-            480..494 'V2([0.0, 1.0])': V2
-            483..493 '[0.0, 1.0]': [f32; 2]
-            484..487 '0.0': f32
-            489..492 '1.0': f32
-            505..506 'r': V2
-            509..511 'va': V2
-            509..516 'va + vb': V2
-            514..516 'vb': V2
-        "#]],
     );
 }
 
diff --git a/crates/hir_ty/src/tests/traits.rs b/crates/hir_ty/src/tests/traits.rs
index de1b0e9..2e07441 100644
--- a/crates/hir_ty/src/tests/traits.rs
+++ b/crates/hir_ty/src/tests/traits.rs
@@ -1798,66 +1798,32 @@
 
 #[test]
 fn closure_2() {
-    check_infer_with_mismatches(
+    check_types(
         r#"
-#[lang = "add"]
-pub trait Add<Rhs = Self> {
-    type Output;
-    fn add(self, rhs: Rhs) -> Self::Output;
-}
+//- minicore: add, fn
 
-trait FnOnce<Args> {
-    type Output;
-}
-
-impl Add for u64 {
+impl core::ops::Add for u64 {
     type Output = Self;
     fn add(self, rhs: u64) -> Self::Output {0}
 }
 
-impl Add for u128 {
+impl core::ops::Add for u128 {
     type Output = Self;
     fn add(self, rhs: u128) -> Self::Output {0}
 }
 
 fn test<F: FnOnce(u32) -> u64>(f: F) {
     f(1);
+  //  ^ u32
+  //^^^^ u64
     let g = |v| v + 1;
+              //^^^^^ u64
+          //^^^^^^^^^ |u64| -> u64
     g(1u64);
+  //^^^^^^^ u64
     let h = |v| 1u128 + v;
+          //^^^^^^^^^^^^^ |u128| -> u128
 }"#,
-        expect![[r#"
-            72..76 'self': Self
-            78..81 'rhs': Rhs
-            203..207 'self': u64
-            209..212 'rhs': u64
-            235..238 '{0}': u64
-            236..237 '0': u64
-            297..301 'self': u128
-            303..306 'rhs': u128
-            330..333 '{0}': u128
-            331..332 '0': u128
-            368..369 'f': F
-            374..450 '{     ...+ v; }': ()
-            380..381 'f': F
-            380..384 'f(1)': {unknown}
-            382..383 '1': i32
-            394..395 'g': |u64| -> u64
-            398..407 '|v| v + 1': |u64| -> u64
-            399..400 'v': u64
-            402..403 'v': u64
-            402..407 'v + 1': u64
-            406..407 '1': u64
-            413..414 'g': |u64| -> u64
-            413..420 'g(1u64)': u64
-            415..419 '1u64': u64
-            430..431 'h': |u128| -> u128
-            434..447 '|v| 1u128 + v': |u128| -> u128
-            435..436 'v': u128
-            438..443 '1u128': u128
-            438..447 '1u128 + v': u128
-            446..447 'v': u128
-        "#]],
     );
 }
 
diff --git a/crates/ide/src/hover.rs b/crates/ide/src/hover.rs
index 09e6156..99e1f6e 100644
--- a/crates/ide/src/hover.rs
+++ b/crates/ide/src/hover.rs
@@ -2773,8 +2773,8 @@
                                     file_id: FileId(
                                         1,
                                     ),
-                                    full_range: 253..435,
-                                    focus_range: 292..298,
+                                    full_range: 254..436,
+                                    focus_range: 293..299,
                                     name: "Future",
                                     kind: Trait,
                                     description: "pub trait Future",
diff --git a/crates/test_utils/src/minicore.rs b/crates/test_utils/src/minicore.rs
index e4ae1f9..5e17047 100644
--- a/crates/test_utils/src/minicore.rs
+++ b/crates/test_utils/src/minicore.rs
@@ -34,6 +34,7 @@
 //!     derive:
 //!     fmt: result
 //!     bool_impl: option, fn
+//!     add:
 
 pub mod marker {
     // region:sized
@@ -302,6 +303,14 @@
     }
     pub use self::try_::{ControlFlow, FromResidual, Try};
     // endregion:try
+
+    // region:add
+    #[lang = "add"]
+    pub trait Add<Rhs = Self> {
+        type Output;
+        fn add(self, rhs: Rhs) -> Self::Output;
+    }
+    // endregion:add
 }
 
 // region:eq