Repository: rust-analyzer/rust-analyzer
Pull request #11780 (2022-03-21 16:00:16)

feat: Add type mismatch diagnostic
This adds a proper diagnostic for type mismatches, turning "Add reference here", "Missing Ok or Some" and "Remove this semicolon" into quickfixes for this single diagnostic.
The diagnostic is marked as experimental when it does not have one of these quickfixes, so it can be turned off with `rust-analyzer.diagnostics.enableExperimental` (or specifically with `rust-analyzer.diagnostics.disabled` of course, the ID is `type-mismatch`).
There will still be some false positives, but I think there shouldn't be too many especially when the Chalk fix lands, and it's still experimental anyway :slightly_smiling_face: 
This also fixes type checking for `rustc_legacy_const_generics` just to avoid some errors in tests.


Patch:
diff --git a/crates/hir/src/diagnostics.rs b/crates/hir/src/diagnostics.rs
index b5e7d5db63..bc43fb15ee 100644
--- a/crates/hir/src/diagnostics.rs
+++ b/crates/hir/src/diagnostics.rs
@@ -5,7 +5,7 @@
 //! be expressed in terms of hir types themselves.
 use cfg::{CfgExpr, CfgOptions};
 use either::Either;
-use hir_def::{path::ModPath, type_ref::Mutability};
+use hir_def::path::ModPath;
 use hir_expand::{name::Name, HirFileId, InFile};
 use syntax::{ast, AstPtr, SyntaxNodePtr, TextRange};
 
@@ -28,7 +28,6 @@ macro_rules! diagnostics {
 }
 
 diagnostics![
-    AddReferenceHere,
     BreakOutsideOfLoop,
     InactiveCode,
     IncorrectCase,
@@ -38,11 +37,10 @@ diagnostics![
     MismatchedArgCount,
     MissingFields,
     MissingMatchArms,
-    MissingOkOrSomeInTailExpr,
     MissingUnsafe,
     NoSuchField,
-    RemoveThisSemicolon,
     ReplaceFilterMapNextWithFindMap,
+    TypeMismatch,
     UnimplementedBuiltinMacro,
     UnresolvedExternCrate,
     UnresolvedImport,
@@ -147,19 +145,6 @@ pub struct MismatchedArgCount {
     pub found: usize,
 }
 
-#[derive(Debug)]
-pub struct RemoveThisSemicolon {
-    pub expr: InFile<AstPtr<ast::Expr>>,
-}
-
-#[derive(Debug)]
-pub struct MissingOkOrSomeInTailExpr {
-    pub expr: InFile<AstPtr<ast::Expr>>,
-    // `Some` or `Ok` depending on whether the return type is Result or Option
-    pub required: String,
-    pub expected: Type,
-}
-
 #[derive(Debug)]
 pub struct MissingMatchArms {
     pub file: HirFileId,
@@ -167,9 +152,11 @@ pub struct MissingMatchArms {
 }
 
 #[derive(Debug)]
-pub struct AddReferenceHere {
+pub struct TypeMismatch {
+    // FIXME: add mismatches in patterns as well
     pub expr: InFile<AstPtr<ast::Expr>>,
-    pub mutability: Mutability,
+    pub expected: Type,
+    pub actual: Type,
 }
 
 pub use hir_ty::diagnostics::IncorrectCase;
diff --git a/crates/hir/src/lib.rs b/crates/hir/src/lib.rs
index 3c12907b82..f4e58d88ed 100644
--- a/crates/hir/src/lib.rs
+++ b/crates/hir/src/lib.rs
@@ -58,7 +58,6 @@ use hir_ty::{
     consteval::{
         eval_const, unknown_const_as_generic, ComputedExpr, ConstEvalCtx, ConstEvalError, ConstExt,
     },
-    could_unify,
     diagnostics::BodyValidationDiagnostic,
     method_resolution::{self, TyFingerprint},
     primitive::UintTy,
@@ -85,12 +84,11 @@ use crate::db::{DefDatabase, HirDatabase};
 pub use crate::{
     attrs::{HasAttrs, Namespace},
     diagnostics::{
-        AddReferenceHere, AnyDiagnostic, BreakOutsideOfLoop, InactiveCode, IncorrectCase,
-        InvalidDeriveTarget, MacroError, MalformedDerive, MismatchedArgCount, MissingFields,
-        MissingMatchArms, MissingOkOrSomeInTailExpr, MissingUnsafe, NoSuchField,
-        RemoveThisSemicolon, ReplaceFilterMapNextWithFindMap, UnimplementedBuiltinMacro,
-        UnresolvedExternCrate, UnresolvedImport, UnresolvedMacroCall, UnresolvedModule,
-        UnresolvedProcMacro,
+        AnyDiagnostic, BreakOutsideOfLoop, InactiveCode, IncorrectCase, InvalidDeriveTarget,
+        MacroError, MalformedDerive, MismatchedArgCount, MissingFields, MissingMatchArms,
+        MissingUnsafe, NoSuchField, ReplaceFilterMapNextWithFindMap, TypeMismatch,
+        UnimplementedBuiltinMacro, UnresolvedExternCrate, UnresolvedImport, UnresolvedMacroCall,
+        UnresolvedModule, UnresolvedProcMacro,
     },
     has_source::HasSource,
     semantics::{PathResolution, Semantics, SemanticsScope, TypeInfo},
@@ -1005,6 +1003,24 @@ impl Adt {
         Type::from_def(db, id.module(db.upcast()).krate(), id)
     }
 
+    /// Turns this ADT into a type with the given type parameters. This isn't
+    /// the greatest API, FIXME find a better one.
+    pub fn ty_with_args(self, db: &dyn HirDatabase, args: &[Type]) -> Type {
+        let id = AdtId::from(self);
+        let mut it = args.iter().map(|t| t.ty.clone());
+        let ty = TyBuilder::def_ty(db, id.into())
+            .fill(|x| {
+                let r = it.next().unwrap_or_else(|| TyKind::Error.intern(Interner));
+                match x {
+                    ParamKind::Type => GenericArgData::Ty(r).intern(Interner),
+                    ParamKind::Const(ty) => unknown_const_as_generic(ty.clone()),
+                }
+            })
+            .build();
+        let krate = id.module(db.upcast()).krate();
+        Type::new(db, krate, id, ty)
+    }
+
     pub fn module(self, db: &dyn HirDatabase) -> Module {
         match self {
             Adt::Struct(s) => s.module(db),
@@ -1020,6 +1036,14 @@ impl Adt {
             Adt::Enum(e) => e.name(db),
         }
     }
+
+    pub fn as_enum(&self) -> Option<Enum> {
+        if let Self::Enum(v) = self {
+            Some(*v)
+        } else {
+            None
+        }
+    }
 }
 
 impl HasVisibility for Adt {
@@ -1163,6 +1187,30 @@ impl DefWithBody {
                 }
             }
         }
+        for (expr, mismatch) in infer.expr_type_mismatches() {
+            let expr = match source_map.expr_syntax(expr) {
+                Ok(expr) => expr,
+                Err(SyntheticSyntax) => continue,
+            };
+            acc.push(
+                TypeMismatch {
+                    expr,
+                    expected: Type::new(
+                        db,
+                        krate,
+                        DefWithBodyId::from(self),
+                        mismatch.expected.clone(),
+                    ),
+                    actual: Type::new(
+                        db,
+                        krate,
+                        DefWithBodyId::from(self),
+                        mismatch.actual.clone(),
+                    ),
+                }
+                .into(),
+            );
+        }
 
         for expr in hir_ty::diagnostics::missing_unsafe(db, self.into()) {
             match source_map.expr_syntax(expr) {
@@ -1259,25 +1307,6 @@ impl DefWithBody {
                         Err(SyntheticSyntax) => (),
                     }
                 }
-                BodyValidationDiagnostic::RemoveThisSemicolon { expr } => {
-                    match source_map.expr_syntax(expr) {
-                        Ok(expr) => acc.push(RemoveThisSemicolon { expr }.into()),
-                        Err(SyntheticSyntax) => (),
-                    }
-                }
-                BodyValidationDiagnostic::MissingOkOrSomeInTailExpr { expr, required } => {
-                    match source_map.expr_syntax(expr) {
-                        Ok(expr) => acc.push(
-                            MissingOkOrSomeInTailExpr {
-                                expr,
-                                required,
-                                expected: self.body_type(db),
-                            }
-                            .into(),
-                        ),
-                        Err(SyntheticSyntax) => (),
-                    }
-                }
                 BodyValidationDiagnostic::MissingMatchArms { match_expr } => {
                     match source_map.expr_syntax(match_expr) {
                         Ok(source_ptr) => {
@@ -1299,12 +1328,6 @@ impl DefWithBody {
                         Err(SyntheticSyntax) => (),
                     }
                 }
-                BodyValidationDiagnostic::AddReferenceHere { arg_expr, mutability } => {
-                    match source_map.expr_syntax(arg_expr) {
-                        Ok(expr) => acc.push(AddReferenceHere { expr, mutability }.into()),
-                        Err(SyntheticSyntax) => (),
-                    }
-                }
             }
         }
 
@@ -2618,6 +2641,17 @@ impl Type {
         Type { krate, env: environment, ty }
     }
 
+    pub fn reference(inner: &Type, m: Mutability) -> Type {
+        inner.derived(
+            TyKind::Ref(
+                if m.is_mut() { hir_ty::Mutability::Mut } else { hir_ty::Mutability::Not },
+                hir_ty::static_lifetime(),
+                inner.ty.clone(),
+            )
+            .intern(Interner),
+        )
+    }
+
     fn new(db: &dyn HirDatabase, krate: CrateId, lexical_env: impl HasResolver, ty: Ty) -> Type {
         let resolver = lexical_env.resolver(db.upcast());
         let environment = resolver
@@ -2659,6 +2693,12 @@ impl Type {
         matches!(self.ty.kind(Interner), TyKind::Ref(..))
     }
 
+    pub fn as_reference(&self) -> Option<(Type, Mutability)> {
+        let (ty, _lt, m) = self.ty.as_reference()?;
+        let m = Mutability::from_mutable(matches!(m, hir_ty::Mutability::Mut));
+        Some((self.derived(ty.clone()), m))
+    }
+
     pub fn is_slice(&self) -> bool {
         matches!(self.ty.kind(Interner), TyKind::Slice(..))
     }
@@ -2900,7 +2940,7 @@ impl Type {
         self.autoderef_(db).map(move |ty| self.derived(ty))
     }
 
-    pub fn autoderef_<'a>(&'a self, db: &'a dyn HirDatabase) -> impl Iterator<Item = Ty> + 'a {
+    fn autoderef_<'a>(&'a self, db: &'a dyn HirDatabase) -> impl Iterator<Item = Ty> + 'a {
         // There should be no inference vars in types passed here
         let canonical = hir_ty::replace_errors_with_variables(&self.ty);
         let environment = self.env.clone();
@@ -3238,7 +3278,12 @@ impl Type {
 
     pub fn could_unify_with(&self, db: &dyn HirDatabase, other: &Type) -> bool {
         let tys = hir_ty::replace_errors_with_variables(&(self.ty.clone(), other.ty.clone()));
-        could_unify(db, self.env.clone(), &tys)
+        hir_ty::could_unify(db, self.env.clone(), &tys)
+    }
+
+    pub fn could_coerce_to(&self, db: &dyn HirDatabase, to: &Type) -> bool {
+        let tys = hir_ty::replace_errors_with_variables(&(self.ty.clone(), to.ty.clone()));
+        hir_ty::could_coerce(db, self.env.clone(), &tys)
     }
 }
 
diff --git a/crates/hir_def/src/type_ref.rs b/crates/hir_def/src/type_ref.rs
index c6c521f733..8e9336a0cc 100644
--- a/crates/hir_def/src/type_ref.rs
+++ b/crates/hir_def/src/type_ref.rs
@@ -38,6 +38,22 @@ impl Mutability {
             Mutability::Mut => "mut ",
         }
     }
+
+    /// Returns `true` if the mutability is [`Mut`].
+    ///
+    /// [`Mut`]: Mutability::Mut
+    #[must_use]
+    pub fn is_mut(&self) -> bool {
+        matches!(self, Self::Mut)
+    }
+
+    /// Returns `true` if the mutability is [`Shared`].
+    ///
+    /// [`Shared`]: Mutability::Shared
+    #[must_use]
+    pub fn is_shared(&self) -> bool {
+        matches!(self, Self::Shared)
+    }
 }
 
 #[derive(Copy, Clone, PartialEq, Eq, Hash, Debug)]
diff --git a/crates/hir_ty/src/diagnostics/expr.rs b/crates/hir_ty/src/diagnostics/expr.rs
index b7d765c59b..71eb7e3995 100644
--- a/crates/hir_ty/src/diagnostics/expr.rs
+++ b/crates/hir_ty/src/diagnostics/expr.rs
@@ -4,10 +4,7 @@
 
 use std::sync::Arc;
 
-use hir_def::{
-    expr::Statement, path::path, resolver::HasResolver, type_ref::Mutability, AssocItemId,
-    DefWithBodyId, HasModule,
-};
+use hir_def::{path::path, resolver::HasResolver, AssocItemId, DefWithBodyId, HasModule};
 use hir_expand::name;
 use itertools::Either;
 use rustc_hash::FxHashSet;
@@ -20,7 +17,7 @@ use crate::{
         deconstruct_pat::DeconstructedPat,
         usefulness::{compute_match_usefulness, MatchCheckCtx},
     },
-    AdtId, InferenceResult, Interner, Ty, TyExt, TyKind,
+    InferenceResult, Interner, TyExt,
 };
 
 pub(crate) use hir_def::{
@@ -43,20 +40,9 @@ pub enum BodyValidationDiagnostic {
         expected: usize,
         found: usize,
     },
-    RemoveThisSemicolon {
-        expr: ExprId,
-    },
-    MissingOkOrSomeInTailExpr {
-        expr: ExprId,
-        required: String,
-    },
     MissingMatchArms {
         match_expr: ExprId,
     },
-    AddReferenceHere {
-        arg_expr: ExprId,
-        mutability: Mutability,
-    },
 }
 
 impl BodyValidationDiagnostic {
@@ -116,30 +102,6 @@ impl ExprValidator {
                 });
             }
         }
-        let body_expr = &body[body.body_expr];
-        if let Expr::Block { statements, tail, .. } = body_expr {
-            if let Some(t) = tail {
-                self.validate_results_in_tail_expr(body.body_expr, *t, db);
-            } else if let Some(Statement::Expr { expr: id, .. }) = statements.last() {
-                self.validate_missing_tail_expr(body.body_expr, *id);
-            }
-        }
-
-        let infer = &self.infer;
-        let diagnostics = &mut self.diagnostics;
-
-        infer
-            .expr_type_mismatches()
-            .filter_map(|(expr, mismatch)| {
-                let (expr_without_ref, mutability) =
-                    check_missing_refs(infer, expr, &mismatch.expected)?;
-
-                Some((expr_without_ref, mutability))
-            })
-            .for_each(|(arg_expr, mutability)| {
-                diagnostics
-                    .push(BodyValidationDiagnostic::AddReferenceHere { arg_expr, mutability });
-            });
     }
 
     fn validate_call(
@@ -330,66 +292,6 @@ impl ExprValidator {
         }
         pattern
     }
-
-    fn validate_results_in_tail_expr(&mut self, body_id: ExprId, id: ExprId, db: &dyn HirDatabase) {
-        // the mismatch will be on the whole block currently
-        let mismatch = match self.infer.type_mismatch_for_expr(body_id) {
-            Some(m) => m,
-            None => return,
-        };
-
-        let core_result_path = path![core::result::Result];
-        let core_option_path = path![core::option::Option];
-
-        let resolver = self.owner.resolver(db.upcast());
-        let core_result_enum = match resolver.resolve_known_enum(db.upcast(), &core_result_path) {
-            Some(it) => it,
-            _ => return,
-        };
-        let core_option_enum = match resolver.resolve_known_enum(db.upcast(), &core_option_path) {
-            Some(it) => it,
-            _ => return,
-        };
-
-        let (params, required) = match mismatch.expected.kind(Interner) {
-            TyKind::Adt(AdtId(hir_def::AdtId::EnumId(enum_id)), parameters)
-                if *enum_id == core_result_enum =>
-            {
-                (parameters, "Ok".to_string())
-            }
-            TyKind::Adt(AdtId(hir_def::AdtId::EnumId(enum_id)), parameters)
-                if *enum_id == core_option_enum =>
-            {
-                (parameters, "Some".to_string())
-            }
-            _ => return,
-        };
-
-        if params.len(Interner) > 0 && params.at(Interner, 0).ty(Interner) == Some(&mismatch.actual)
-        {
-            self.diagnostics
-                .push(BodyValidationDiagnostic::MissingOkOrSomeInTailExpr { expr: id, required });
-        }
-    }
-
-    fn validate_missing_tail_expr(&mut self, body_id: ExprId, possible_tail_id: ExprId) {
-        let mismatch = match self.infer.type_mismatch_for_expr(body_id) {
-            Some(m) => m,
-            None => return,
-        };
-
-        let possible_tail_ty = match self.infer.type_of_expr.get(possible_tail_id) {
-            Some(ty) => ty,
-            None => return,
-        };
-
-        if !mismatch.actual.is_unit() || mismatch.expected != *possible_tail_ty {
-            return;
-        }
-
-        self.diagnostics
-            .push(BodyValidationDiagnostic::RemoveThisSemicolon { expr: possible_tail_id });
-    }
 }
 
 struct FilterMapNextChecker {
@@ -523,30 +425,3 @@ fn types_of_subpatterns_do_match(pat: PatId, body: &Body, infer: &InferenceResul
     walk(pat, body, infer, &mut has_type_mismatches);
     !has_type_mismatches
 }
-
-fn check_missing_refs(
-    infer: &InferenceResult,
-    arg: ExprId,
-    param: &Ty,
-) -> Option<(ExprId, Mutability)> {
-    let arg_ty = infer.type_of_expr.get(arg)?;
-
-    let reference_one = arg_ty.as_reference();
-    let reference_two = param.as_reference();
-
-    match (reference_one, reference_two) {
-        (None, Some((referenced_ty, _, mutability))) if referenced_ty == arg_ty => {
-            Some((arg, Mutability::from_mutable(matches!(mutability, chalk_ir::Mutability::Mut))))
-        }
-        (None, Some((referenced_ty, _, mutability))) => match referenced_ty.kind(Interner) {
-            TyKind::Slice(subst) if matches!(arg_ty.kind(Interner), TyKind::Array(arr_subst, _) if arr_subst == subst) => {
-                Some((
-                    arg,
-                    Mutability::from_mutable(matches!(mutability, chalk_ir::Mutability::Mut)),
-                ))
-            }
-            _ => None,
-        },
-        _ => None,
-    }
-}
diff --git a/crates/hir_ty/src/infer.rs b/crates/hir_ty/src/infer.rs
index 442774d0be..c91cdec183 100644
--- a/crates/hir_ty/src/infer.rs
+++ b/crates/hir_ty/src/infer.rs
@@ -44,6 +44,8 @@ use crate::{
 //
 // https://github.com/rust-lang/rust/issues/57411
 #[allow(unreachable_pub)]
+pub use coerce::could_coerce;
+#[allow(unreachable_pub)]
 pub use unify::could_unify;
 
 pub(crate) mod unify;
diff --git a/crates/hir_ty/src/infer/coerce.rs b/crates/hir_ty/src/infer/coerce.rs
index 528e3ba882..f54440bf5b 100644
--- a/crates/hir_ty/src/infer/coerce.rs
+++ b/crates/hir_ty/src/infer/coerce.rs
@@ -5,23 +5,26 @@
 //! See <https://doc.rust-lang.org/nomicon/coercions.html> and
 //! `librustc_typeck/check/coercion.rs`.
 
-use std::iter;
+use std::{iter, sync::Arc};
 
-use chalk_ir::{cast::Cast, Goal, Mutability, TyVariableKind};
+use chalk_ir::{cast::Cast, BoundVar, Goal, Mutability, TyVariableKind};
 use hir_def::{expr::ExprId, lang_item::LangItemTarget};
 use stdx::always;
 use syntax::SmolStr;
 
 use crate::{
     autoderef::{Autoderef, AutoderefKind},
+    db::HirDatabase,
     infer::{
-        Adjust, Adjustment, AutoBorrow, InferOk, InferResult, InferenceContext, OverloadedDeref,
-        PointerCast, TypeError, TypeMismatch,
+        Adjust, Adjustment, AutoBorrow, InferOk, InferenceContext, OverloadedDeref, PointerCast,
+        TypeError, TypeMismatch,
     },
     static_lifetime, Canonical, DomainGoal, FnPointer, FnSig, Guidance, InEnvironment, Interner,
-    Solution, Substitution, Ty, TyBuilder, TyExt, TyKind,
+    Solution, Substitution, TraitEnvironment, Ty, TyBuilder, TyExt, TyKind,
 };
 
+use super::unify::InferenceTable;
+
 pub(crate) type CoerceResult = Result<InferOk<(Vec<Adjustment>, Ty)>, TypeError>;
 
 /// Do not require any adjustments, i.e. coerce `x -> x`.
@@ -84,8 +87,8 @@ impl CoerceMany {
         };
         if let Some(sig) = sig {
             let target_ty = TyKind::Function(sig.to_fn_ptr()).intern(Interner);
-            let result1 = ctx.coerce_inner(self.expected_ty.clone(), &target_ty);
-            let result2 = ctx.coerce_inner(expr_ty.clone(), &target_ty);
+            let result1 = ctx.table.coerce_inner(self.expected_ty.clone(), &target_ty);
+            let result2 = ctx.table.coerce_inner(expr_ty.clone(), &target_ty);
             if let (Ok(result1), Ok(result2)) = (result1, result2) {
                 ctx.table.register_infer_ok(result1);
                 ctx.table.register_infer_ok(result2);
@@ -118,6 +121,45 @@ impl CoerceMany {
     }
 }
 
+pub fn could_coerce(
+    db: &dyn HirDatabase,
+    env: Arc<TraitEnvironment>,
+    tys: &Canonical<(Ty, Ty)>,
+) -> bool {
+    coerce(db, env, tys).is_ok()
+}
+
+pub(crate) fn coerce(
+    db: &dyn HirDatabase,
+    env: Arc<TraitEnvironment>,
+    tys: &Canonical<(Ty, Ty)>,
+) -> Result<(Vec<Adjustment>, Ty), TypeError> {
+    let mut table = InferenceTable::new(db, env);
+    let vars = table.fresh_subst(tys.binders.as_slice(Interner));
+    let ty1_with_vars = vars.apply(tys.value.0.clone(), Interner);
+    let ty2_with_vars = vars.apply(tys.value.1.clone(), Interner);
+    let (adjustments, ty) = table.coerce(&ty1_with_vars, &ty2_with_vars)?;
+    // default any type vars that weren't unified back to their original bound vars
+    // (kind of hacky)
+    let find_var = |iv| {
+        vars.iter(Interner).position(|v| match v.interned() {
+            chalk_ir::GenericArgData::Ty(ty) => ty.inference_var(Interner),
+            chalk_ir::GenericArgData::Lifetime(lt) => lt.inference_var(Interner),
+            chalk_ir::GenericArgData::Const(c) => c.inference_var(Interner),
+        } == Some(iv))
+    };
+    let fallback = |iv, kind, default, binder| match kind {
+        chalk_ir::VariableKind::Ty(_ty_kind) => find_var(iv)
+            .map_or(default, |i| BoundVar::new(binder, i).to_ty(Interner).cast(Interner)),
+        chalk_ir::VariableKind::Lifetime => find_var(iv)
+            .map_or(default, |i| BoundVar::new(binder, i).to_lifetime(Interner).cast(Interner)),
+        chalk_ir::VariableKind::Const(ty) => find_var(iv)
+            .map_or(default, |i| BoundVar::new(binder, i).to_const(Interner, ty).cast(Interner)),
+    };
+    // FIXME also map the types in the adjustments
+    Ok((adjustments, table.resolve_with_fallback(ty, &fallback)))
+}
+
 impl<'a> InferenceContext<'a> {
     /// Unify two types, but may coerce the first one to the second one
     /// using "implicit coercion rules" if needed.
@@ -126,16 +168,31 @@ impl<'a> InferenceContext<'a> {
         expr: Option<ExprId>,
         from_ty: &Ty,
         to_ty: &Ty,
-    ) -> InferResult<Ty> {
+    ) -> Result<Ty, TypeError> {
+        let from_ty = self.resolve_ty_shallow(from_ty);
+        let to_ty = self.resolve_ty_shallow(to_ty);
+        let (adjustments, ty) = self.table.coerce(&from_ty, &to_ty)?;
+        if let Some(expr) = expr {
+            self.write_expr_adj(expr, adjustments);
+        }
+        Ok(ty)
+    }
+}
+
+impl<'a> InferenceTable<'a> {
+    /// Unify two types, but may coerce the first one to the second one
+    /// using "implicit coercion rules" if needed.
+    pub(crate) fn coerce(
+        &mut self,
+        from_ty: &Ty,
+        to_ty: &Ty,
+    ) -> Result<(Vec<Adjustment>, Ty), TypeError> {
         let from_ty = self.resolve_ty_shallow(from_ty);
         let to_ty = self.resolve_ty_shallow(to_ty);
         match self.coerce_inner(from_ty, &to_ty) {
             Ok(InferOk { value: (adjustments, ty), goals }) => {
-                if let Some(expr) = expr {
-                    self.write_expr_adj(expr, adjustments);
-                }
-                self.table.register_infer_ok(InferOk { value: (), goals });
-                Ok(InferOk { value: ty, goals: Vec::new() })
+                self.register_infer_ok(InferOk { value: (), goals });
+                Ok((adjustments, ty))
             }
             Err(e) => {
                 // FIXME deal with error
@@ -154,7 +211,7 @@ impl<'a> InferenceContext<'a> {
             //
             // here, we would coerce from `!` to `?T`.
             if let TyKind::InferenceVar(tv, TyVariableKind::General) = to_ty.kind(Interner) {
-                self.table.set_diverging(*tv, true);
+                self.set_diverging(*tv, true);
             }
             return success(simple(Adjust::NeverToAny)(to_ty.clone()), to_ty.clone(), vec![]);
         }
@@ -203,8 +260,7 @@ impl<'a> InferenceContext<'a> {
     where
         F: FnOnce(Ty) -> Vec<Adjustment>,
     {
-        self.table
-            .try_unify(t1, t2)
+        self.try_unify(t1, t2)
             .and_then(|InferOk { goals, .. }| success(f(t1.clone()), t1.clone(), goals))
     }
 
@@ -259,9 +315,9 @@ impl<'a> InferenceContext<'a> {
         // details of coercion errors though, so I think it's useful to leave
         // the structure like it is.
 
-        let snapshot = self.table.snapshot();
+        let snapshot = self.snapshot();
 
-        let mut autoderef = Autoderef::new(&mut self.table, from_ty.clone());
+        let mut autoderef = Autoderef::new(self, from_ty.clone());
         let mut first_error = None;
         let mut found = None;
 
@@ -317,7 +373,7 @@ impl<'a> InferenceContext<'a> {
         let InferOk { value: ty, goals } = match found {
             Some(d) => d,
             None => {
-                self.table.rollback_to(snapshot);
+                self.rollback_to(snapshot);
                 let err = first_error.expect("coerce_borrowed_pointer had no error");
                 return Err(err);
             }
@@ -513,7 +569,7 @@ impl<'a> InferenceContext<'a> {
         let coerce_from =
             reborrow.as_ref().map_or_else(|| from_ty.clone(), |(_, adj)| adj.target.clone());
 
-        let krate = self.resolver.krate().unwrap();
+        let krate = self.trait_env.krate;
         let coerce_unsized_trait =
             match self.db.lang_item(krate, SmolStr::new_inline("coerce_unsized")) {
                 Some(LangItemTarget::TraitId(trait_)) => trait_,
@@ -546,7 +602,7 @@ impl<'a> InferenceContext<'a> {
         match solution {
             Solution::Unique(v) => {
                 canonicalized.apply_solution(
-                    &mut self.table,
+                    self,
                     Canonical {
                         binders: v.binders,
                         // FIXME handle constraints
@@ -556,7 +612,7 @@ impl<'a> InferenceContext<'a> {
             }
             Solution::Ambig(Guidance::Definite(subst)) => {
                 // FIXME need to record an obligation here
-                canonicalized.apply_solution(&mut self.table, subst)
+                canonicalized.apply_solution(self, subst)
             }
             // FIXME actually we maybe should also accept unknown guidance here
             _ => return Err(TypeError),
diff --git a/crates/hir_ty/src/infer/expr.rs b/crates/hir_ty/src/infer/expr.rs
index ebbce33e01..43cff92f23 100644
--- a/crates/hir_ty/src/infer/expr.rs
+++ b/crates/hir_ty/src/infer/expr.rs
@@ -28,7 +28,7 @@ use crate::{
     lower::{
         const_or_path_to_chalk, generic_arg_to_chalk, lower_to_chalk_mutability, ParamLoweringMode,
     },
-    mapping::from_chalk,
+    mapping::{from_chalk, ToChalk},
     method_resolution,
     primitive::{self, UintTy},
     static_lifetime, to_chalk_trait_id,
@@ -67,7 +67,7 @@ impl<'a> InferenceContext<'a> {
         let ty = self.infer_expr_inner(expr, expected);
         if let Some(target) = expected.only_has_type(&mut self.table) {
             match self.coerce(Some(expr), &ty, &target) {
-                Ok(res) => res.value,
+                Ok(res) => res,
                 Err(_) => {
                     self.result
                         .type_mismatches
@@ -279,14 +279,16 @@ impl<'a> InferenceContext<'a> {
                 let callee_ty = self.infer_expr(*callee, &Expectation::none());
                 let mut derefs = Autoderef::new(&mut self.table, callee_ty.clone());
                 let mut res = None;
+                let mut derefed_callee = callee_ty.clone();
                 // manual loop to be able to access `derefs.table`
                 while let Some((callee_deref_ty, _)) = derefs.next() {
                     res = derefs.table.callable_sig(&callee_deref_ty, args.len());
                     if res.is_some() {
+                        derefed_callee = callee_deref_ty;
                         break;
                     }
                 }
-                let (param_tys, ret_ty): (Vec<Ty>, Ty) = match res {
+                let (param_tys, ret_ty) = match res {
                     Some(res) => {
                         let adjustments = auto_deref_adjust_steps(&derefs);
                         self.write_expr_adj(*callee, adjustments);
@@ -294,6 +296,7 @@ impl<'a> InferenceContext<'a> {
                     }
                     None => (Vec::new(), self.err_ty()),
                 };
+                let indices_to_skip = self.check_legacy_const_generics(derefed_callee, args);
                 self.register_obligations_for_call(&callee_ty);
 
                 let expected_inputs = self.expected_inputs_for_expected_output(
@@ -302,7 +305,7 @@ impl<'a> InferenceContext<'a> {
                     param_tys.clone(),
                 );
 
-                self.check_call_arguments(args, &expected_inputs, &param_tys);
+                self.check_call_arguments(args, &expected_inputs, &param_tys, &indices_to_skip);
                 self.normalize_associated_types_in(ret_ty)
             }
             Expr::MethodCall { receiver, args, method_name, generic_args } => self
@@ -952,7 +955,7 @@ impl<'a> InferenceContext<'a> {
         let expected_inputs =
             self.expected_inputs_for_expected_output(expected, ret_ty.clone(), param_tys.clone());
 
-        self.check_call_arguments(args, &expected_inputs, &param_tys);
+        self.check_call_arguments(args, &expected_inputs, &param_tys, &[]);
         self.normalize_associated_types_in(ret_ty)
     }
 
@@ -983,24 +986,40 @@ impl<'a> InferenceContext<'a> {
         }
     }
 
-    fn check_call_arguments(&mut self, args: &[ExprId], expected_inputs: &[Ty], param_tys: &[Ty]) {
+    fn check_call_arguments(
+        &mut self,
+        args: &[ExprId],
+        expected_inputs: &[Ty],
+        param_tys: &[Ty],
+        skip_indices: &[u32],
+    ) {
         // Quoting https://github.com/rust-lang/rust/blob/6ef275e6c3cb1384ec78128eceeb4963ff788dca/src/librustc_typeck/check/mod.rs#L3325 --
         // We do this in a pretty awful way: first we type-check any arguments
         // that are not closures, then we type-check the closures. This is so
         // that we have more information about the types of arguments when we
         // type-check the functions. This isn't really the right way to do this.
         for &check_closures in &[false, true] {
+            let mut skip_indices = skip_indices.into_iter().copied().fuse().peekable();
             let param_iter = param_tys.iter().cloned().chain(repeat(self.err_ty()));
             let expected_iter = expected_inputs
                 .iter()
                 .cloned()
                 .chain(param_iter.clone().skip(expected_inputs.len()));
-            for ((&arg, param_ty), expected_ty) in args.iter().zip(param_iter).zip(expected_iter) {
+            for (idx, ((&arg, param_ty), expected_ty)) in
+                args.iter().zip(param_iter).zip(expected_iter).enumerate()
+            {
                 let is_closure = matches!(&self.body[arg], Expr::Lambda { .. });
                 if is_closure != check_closures {
                     continue;
                 }
 
+                while skip_indices.peek().map_or(false, |i| *i < idx as u32) {
+                    skip_indices.next();
+                }
+                if skip_indices.peek().copied() == Some(idx as u32) {
+                    continue;
+                }
+
                 // the difference between param_ty and expected here is that
                 // expected is the parameter when the expected *return* type is
                 // taken into account. So in `let _: &[i32] = identity(&[1, 2])`
@@ -1140,6 +1159,49 @@ impl<'a> InferenceContext<'a> {
         }
     }
 
+    /// Returns the argument indices to skip.
+    fn check_legacy_const_generics(&mut self, callee: Ty, args: &[ExprId]) -> Vec<u32> {
+        let (func, subst) = match callee.kind(Interner) {
+            TyKind::FnDef(fn_id, subst) => {
+                let callable = CallableDefId::from_chalk(self.db, *fn_id);
+                let func = match callable {
+                    CallableDefId::FunctionId(f) => f,
+                    _ => return Vec::new(),
+                };
+                (func, subst)
+            }
+            _ => return Vec::new(),
+        };
+
+        let data = self.db.function_data(func);
+        if data.legacy_const_generics_indices.is_empty() {
+            return Vec::new();
+        }
+
+        // only use legacy const generics if the param count matches with them
+        if data.params.len() + data.legacy_const_generics_indices.len() != args.len() {
+            return Vec::new();
+        }
+
+        // check legacy const parameters
+        for (subst_idx, arg_idx) in data.legacy_const_generics_indices.iter().copied().enumerate() {
+            let arg = match subst.at(Interner, subst_idx).constant(Interner) {
+                Some(c) => c,
+                None => continue, // not a const parameter?
+            };
+            if arg_idx >= args.len() as u32 {
+                continue;
+            }
+            let _ty = arg.data(Interner).ty.clone();
+            let expected = Expectation::none(); // FIXME use actual const ty, when that is lowered correctly
+            self.infer_expr(args[arg_idx as usize], &expected);
+            // FIXME: evaluate and unify with the const
+        }
+        let mut indices = data.legacy_const_generics_indices.clone();
+        indices.sort();
+        indices
+    }
+
     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);
diff --git a/crates/hir_ty/src/infer/unify.rs b/crates/hir_ty/src/infer/unify.rs
index ef0675d59f..84ca1660af 100644
--- a/crates/hir_ty/src/infer/unify.rs
+++ b/crates/hir_ty/src/infer/unify.rs
@@ -3,8 +3,8 @@
 use std::{fmt, mem, sync::Arc};
 
 use chalk_ir::{
-    cast::Cast, fold::Fold, interner::HasInterner, zip::Zip, FloatTy, IntTy, NoSolution,
-    TyVariableKind, UniverseIndex,
+    cast::Cast, fold::Fold, interner::HasInterner, zip::Zip, CanonicalVarKind, FloatTy, IntTy,
+    NoSolution, TyVariableKind, UniverseIndex,
 };
 use chalk_solve::infer::ParameterEnaVariableExt;
 use ena::unify::UnifyKey;
@@ -299,11 +299,23 @@ impl<'a> InferenceTable<'a> {
         self.resolve_with_fallback_inner(&mut Vec::new(), t, &fallback)
     }
 
+    pub(crate) fn fresh_subst(&mut self, binders: &[CanonicalVarKind<Interner>]) -> Substitution {
+        Substitution::from_iter(
+            Interner,
+            binders.iter().map(|kind| {
+                let param_infer_var =
+                    kind.map_ref(|&ui| self.var_unification_table.new_variable(ui));
+                param_infer_var.to_generic_arg(Interner)
+            }),
+        )
+    }
+
     pub(crate) fn instantiate_canonical<T>(&mut self, canonical: Canonical<T>) -> T::Result
     where
         T: HasInterner<Interner = Interner> + Fold<Interner> + std::fmt::Debug,
     {
-        self.var_unification_table.instantiate_canonical(Interner, canonical)
+        let subst = self.fresh_subst(canonical.binders.as_slice(Interner));
+        subst.apply(canonical.value, Interner)
     }
 
     fn resolve_with_fallback_inner<T>(
diff --git a/crates/hir_ty/src/lib.rs b/crates/hir_ty/src/lib.rs
index 59e6fe2a04..945b4b0e4a 100644
--- a/crates/hir_ty/src/lib.rs
+++ b/crates/hir_ty/src/lib.rs
@@ -51,7 +51,7 @@ pub use autoderef::autoderef;
 pub use builder::{ParamKind, TyBuilder};
 pub use chalk_ext::*;
 pub use infer::{
-    could_unify, Adjust, Adjustment, AutoBorrow, InferenceDiagnostic, InferenceResult,
+    could_coerce, could_unify, Adjust, Adjustment, AutoBorrow, InferenceDiagnostic, InferenceResult,
 };
 pub use interner::Interner;
 pub use lower::{
diff --git a/crates/ide_diagnostics/src/handlers/add_reference_here.rs b/crates/ide_diagnostics/src/handlers/add_reference_here.rs
deleted file mode 100644
index db24fd6cce..0000000000
--- a/crates/ide_diagnostics/src/handlers/add_reference_here.rs
+++ /dev/null
@@ -1,163 +0,0 @@
-use hir::db::AstDatabase;
-use ide_db::source_change::SourceChange;
-use syntax::AstNode;
-use text_edit::TextEdit;
-
-use crate::{fix, Assist, Diagnostic, DiagnosticsContext};
-
-// Diagnostic: add-reference-here
-//
-// This diagnostic is triggered when there's a missing referencing of expression.
-pub(crate) fn add_reference_here(
-    ctx: &DiagnosticsContext<'_>,
-    d: &hir::AddReferenceHere,
-) -> Diagnostic {
-    Diagnostic::new(
-        "add-reference-here",
-        "add reference here",
-        ctx.sema.diagnostics_display_range(d.expr.clone().map(|it| it.into())).range,
-    )
-    .with_fixes(fixes(ctx, d))
-}
-
-fn fixes(ctx: &DiagnosticsContext<'_>, d: &hir::AddReferenceHere) -> Option<Vec<Assist>> {
-    let root = ctx.sema.db.parse_or_expand(d.expr.file_id)?;
-    let arg_expr = d.expr.value.to_node(&root);
-
-    let arg_with_ref = format!("&{}{}", d.mutability.as_keyword_for_ref(), arg_expr.syntax());
-
-    let arg_range = arg_expr.syntax().text_range();
-    let edit = TextEdit::replace(arg_range, arg_with_ref);
-    let source_change =
-        SourceChange::from_text_edit(d.expr.file_id.original_file(ctx.sema.db), edit);
-
-    Some(vec![fix("add_reference_here", "Add reference here", source_change, arg_range)])
-}
-
-#[cfg(test)]
-mod tests {
-    use crate::tests::{check_diagnostics, check_fix};
-
-    #[test]
-    fn missing_reference() {
-        check_diagnostics(
-            r#"
-fn main() {
-    test(123);
-       //^^^ 💡 error: add reference here
-}
-fn test(arg: &i32) {}
-"#,
-        );
-    }
-
-    #[test]
-    fn test_add_reference_to_int() {
-        check_fix(
-            r#"
-fn main() {
-    test(123$0);
-}
-fn test(arg: &i32) {}
-            "#,
-            r#"
-fn main() {
-    test(&123);
-}
-fn test(arg: &i32) {}
-            "#,
-        );
-    }
-
-    #[test]
-    fn test_add_mutable_reference_to_int() {
-        check_fix(
-            r#"
-fn main() {
-    test($0123);
-}
-fn test(arg: &mut i32) {}
-            "#,
-            r#"
-fn main() {
-    test(&mut 123);
-}
-fn test(arg: &mut i32) {}
-            "#,
-        );
-    }
-
-    #[test]
-    fn test_add_reference_to_array() {
-        check_fix(
-            r#"
-fn main() {
-    test($0[1, 2, 3]);
-}
-fn test(arg: &[i32]) {}
-            "#,
-            r#"
-fn main() {
-    test(&[1, 2, 3]);
-}
-fn test(arg: &[i32]) {}
-            "#,
-        );
-    }
-
-    #[test]
-    fn test_add_reference_to_method_call() {
-        check_fix(
-            r#"
-fn main() {
-    Test.call_by_ref($0123);
-}
-struct Test;
-impl Test {
-    fn call_by_ref(&self, arg: &i32) {}
-}
-            "#,
-            r#"
-fn main() {
-    Test.call_by_ref(&123);
-}
-struct Test;
-impl Test {
-    fn call_by_ref(&self, arg: &i32) {}
-}
-            "#,
-        );
-    }
-
-    #[test]
-    fn test_add_reference_to_let_stmt() {
-        check_fix(
-            r#"
-fn main() {
-    let test: &i32 = $0123;
-}
-            "#,
-            r#"
-fn main() {
-    let test: &i32 = &123;
-}
-            "#,
-        );
-    }
-
-    #[test]
-    fn test_add_mutable_reference_to_let_stmt() {
-        check_fix(
-            r#"
-fn main() {
-    let test: &mut i32 = $0123;
-}
-            "#,
-            r#"
-fn main() {
-    let test: &mut i32 = &mut 123;
-}
-            "#,
-        );
-    }
-}
diff --git a/crates/ide_diagnostics/src/handlers/field_shorthand.rs b/crates/ide_diagnostics/src/handlers/field_shorthand.rs
index 33152e2845..2b71053625 100644
--- a/crates/ide_diagnostics/src/handlers/field_shorthand.rs
+++ b/crates/ide_diagnostics/src/handlers/field_shorthand.rs
@@ -108,13 +108,13 @@ mod tests {
         check_diagnostics(
             r#"
 struct A { a: &'static str }
-fn main() { A { a: "hello" } }
+fn main() { A { a: "hello" }; }
 "#,
         );
         check_diagnostics(
             r#"
 struct A(usize);
-fn main() { A { 0: 0 } }
+fn main() { A { 0: 0 }; }
 "#,
         );
 
@@ -123,14 +123,14 @@ fn main() { A { 0: 0 } }
 struct A { a: &'static str }
 fn main() {
     let a = "haha";
-    A { a$0: a }
+    A { a$0: a };
 }
 "#,
             r#"
 struct A { a: &'static str }
 fn main() {
     let a = "haha";
-    A { a }
+    A { a };
 }
 "#,
         );
@@ -141,7 +141,7 @@ struct A { a: &'static str, b: &'static str }
 fn main() {
     let a = "haha";
     let b = "bb";
-    A { a$0: a, b }
+    A { a$0: a, b };
 }
 "#,
             r#"
@@ -149,7 +149,7 @@ struct A { a: &'static str, b: &'static str }
 fn main() {
     let a = "haha";
     let b = "bb";
-    A { a, b }
+    A { a, b };
 }
 "#,
         );
diff --git a/crates/ide_diagnostics/src/handlers/missing_match_arms.rs b/crates/ide_diagnostics/src/handlers/missing_match_arms.rs
index 6bdcd41a79..fe6a8683c1 100644
--- a/crates/ide_diagnostics/src/handlers/missing_match_arms.rs
+++ b/crates/ide_diagnostics/src/handlers/missing_match_arms.rs
@@ -278,6 +278,7 @@ fn main() {
     match (true, false) {
         (true, false, true) => (),
         (true) => (),
+      // ^^^^  error: expected (bool, bool), found bool
     }
     match (true, false) { (true,) => {} }
     match (0) { () => () }
diff --git a/crates/ide_diagnostics/src/handlers/missing_ok_or_some_in_tail_expr.rs b/crates/ide_diagnostics/src/handlers/missing_ok_or_some_in_tail_expr.rs
deleted file mode 100644
index d5635ba8ba..0000000000
--- a/crates/ide_diagnostics/src/handlers/missing_ok_or_some_in_tail_expr.rs
+++ /dev/null
@@ -1,223 +0,0 @@
-use hir::{db::AstDatabase, TypeInfo};
-use ide_db::{
-    assists::Assist, source_change::SourceChange, syntax_helpers::node_ext::for_each_tail_expr,
-};
-use syntax::AstNode;
-use text_edit::TextEdit;
-
-use crate::{fix, Diagnostic, DiagnosticsContext};
-
-// Diagnostic: missing-ok-or-some-in-tail-expr
-//
-// This diagnostic is triggered if a block that should return `Result` returns a value not wrapped in `Ok`,
-// or if a block that should return `Option` returns a value not wrapped in `Some`.
-//
-// Example:
-//
-// ```rust
-// fn foo() -> Result<u8, ()> {
-//     10
-// }
-// ```
-pub(crate) fn missing_ok_or_some_in_tail_expr(
-    ctx: &DiagnosticsContext<'_>,
-    d: &hir::MissingOkOrSomeInTailExpr,
-) -> Diagnostic {
-    Diagnostic::new(
-        "missing-ok-or-some-in-tail-expr",
-        format!("wrap return expression in {}", d.required),
-        ctx.sema.diagnostics_display_range(d.expr.clone().map(|it| it.into())).range,
-    )
-    .with_fixes(fixes(ctx, d))
-}
-
-fn fixes(ctx: &DiagnosticsContext<'_>, d: &hir::MissingOkOrSomeInTailExpr) -> Option<Vec<Assist>> {
-    let root = ctx.sema.db.parse_or_expand(d.expr.file_id)?;
-    let tail_expr = d.expr.value.to_node(&root);
-    let tail_expr_range = tail_expr.syntax().text_range();
-    let mut builder = TextEdit::builder();
-    for_each_tail_expr(&tail_expr, &mut |expr| {
-        if ctx.sema.type_of_expr(expr).map(TypeInfo::original).as_ref() != Some(&d.expected) {
-            builder.insert(expr.syntax().text_range().start(), format!("{}(", d.required));
-            builder.insert(expr.syntax().text_range().end(), ")".to_string());
-        }
-    });
-    let source_change =
-        SourceChange::from_text_edit(d.expr.file_id.original_file(ctx.sema.db), builder.finish());
-    let name = if d.required == "Ok" { "Wrap with Ok" } else { "Wrap with Some" };
-    Some(vec![fix("wrap_tail_expr", name, source_change, tail_expr_range)])
-}
-
-#[cfg(test)]
-mod tests {
-    use crate::tests::{check_diagnostics, check_fix};
-
-    #[test]
-    fn test_wrap_return_type_option() {
-        check_fix(
-            r#"
-//- minicore: option, result
-fn div(x: i32, y: i32) -> Option<i32> {
-    if y == 0 {
-        return None;
-    }
-    x / y$0
-}
-"#,
-            r#"
-fn div(x: i32, y: i32) -> Option<i32> {
-    if y == 0 {
-        return None;
-    }
-    Some(x / y)
-}
-"#,
-        );
-    }
-
-    #[test]
-    fn test_wrap_return_type_option_tails() {
-        check_fix(
-            r#"
-//- minicore: option, result
-fn div(x: i32, y: i32) -> Option<i32> {
-    if y == 0 {
-        0
-    } else if true {
-        100
-    } else {
-        None
-    }$0
-}
-"#,
-            r#"
-fn div(x: i32, y: i32) -> Option<i32> {
-    if y == 0 {
-        Some(0)
-    } else if true {
-        Some(100)
-    } else {
-        None
-    }
-}
-"#,
-        );
-    }
-
-    #[test]
-    fn test_wrap_return_type() {
-        check_fix(
-            r#"
-//- minicore: option, result
-fn div(x: i32, y: i32) -> Result<i32, ()> {
-    if y == 0 {
-        return Err(());
-    }
-    x / y$0
-}
-"#,
-            r#"
-fn div(x: i32, y: i32) -> Result<i32, ()> {
-    if y == 0 {
-        return Err(());
-    }
-    Ok(x / y)
-}
-"#,
-        );
-    }
-
-    #[test]
-    fn test_wrap_return_type_handles_generic_functions() {
-        check_fix(
-            r#"
-//- minicore: option, result
-fn div<T>(x: T) -> Result<T, i32> {
-    if x == 0 {
-        return Err(7);
-    }
-    $0x
-}
-"#,
-            r#"
-fn div<T>(x: T) -> Result<T, i32> {
-    if x == 0 {
-        return Err(7);
-    }
-    Ok(x)
-}
-"#,
-        );
-    }
-
-    #[test]
-    fn test_wrap_return_type_handles_type_aliases() {
-        check_fix(
-            r#"
-//- minicore: option, result
-type MyResult<T> = Result<T, ()>;
-
-fn div(x: i32, y: i32) -> MyResult<i32> {
-    if y == 0 {
-        return Err(());
-    }
-    x $0/ y
-}
-"#,
-            r#"
-type MyResult<T> = Result<T, ()>;
-
-fn div(x: i32, y: i32) -> MyResult<i32> {
-    if y == 0 {
-        return Err(());
-    }
-    Ok(x / y)
-}
-"#,
-        );
-    }
-
-    #[test]
-    fn test_in_const_and_static() {
-        check_fix(
-            r#"
-//- minicore: option, result
-static A: Option<()> = {($0)};
-            "#,
-            r#"
-static A: Option<()> = {Some(())};
-            "#,
-        );
-        check_fix(
-            r#"
-//- minicore: option, result
-const _: Option<()> = {($0)};
-            "#,
-            r#"
-const _: Option<()> = {Some(())};
-            "#,
-        );
-    }
-
-    #[test]
-    fn test_wrap_return_type_not_applicable_when_expr_type_does_not_match_ok_type() {
-        check_diagnostics(
-            r#"
-//- minicore: option, result
-fn foo() -> Result<(), i32> { 0 }
-"#,
-        );
-    }
-
-    #[test]
-    fn test_wrap_return_type_not_applicable_when_return_type_is_not_result_or_option() {
-        check_diagnostics(
-            r#"
-//- minicore: option, result
-enum SomeOtherEnum { Ok(i32), Err(String) }
-
-fn foo() -> SomeOtherEnum { 0 }
-"#,
-        );
-    }
-}
diff --git a/crates/ide_diagnostics/src/handlers/remove_this_semicolon.rs b/crates/ide_diagnostics/src/handlers/remove_this_semicolon.rs
deleted file mode 100644
index 141fbc42fa..0000000000
--- a/crates/ide_diagnostics/src/handlers/remove_this_semicolon.rs
+++ /dev/null
@@ -1,76 +0,0 @@
-use ide_db::{
-    base_db::{FileLoader, FileRange},
-    source_change::SourceChange,
-};
-use syntax::{TextRange, TextSize};
-use text_edit::TextEdit;
-
-use crate::{fix, Assist, Diagnostic, DiagnosticsContext};
-
-// Diagnostic: remove-this-semicolon
-//
-// This diagnostic is triggered when there's an erroneous `;` at the end of the block.
-pub(crate) fn remove_this_semicolon(
-    ctx: &DiagnosticsContext<'_>,
-    d: &hir::RemoveThisSemicolon,
-) -> Diagnostic {
-    Diagnostic::new(
-        "remove-this-semicolon",
-        "remove this semicolon",
-        semicolon_range(ctx, d).unwrap_or_else(|it| it).range,
-    )
-    .with_fixes(fixes(ctx, d))
-}
-
-fn semicolon_range(
-    ctx: &DiagnosticsContext<'_>,
-    d: &hir::RemoveThisSemicolon,
-) -> Result<FileRange, FileRange> {
-    let expr_range = ctx.sema.diagnostics_display_range(d.expr.clone().map(|it| it.into()));
-    let file_text = ctx.sema.db.file_text(expr_range.file_id);
-    let range_end: usize = expr_range.range.end().into();
-    // FIXME: This doesn't handle whitespace and comments, but handling those in
-    // the presence of macros might prove tricky...
-    if file_text[range_end..].starts_with(';') {
-        Ok(FileRange {
-            file_id: expr_range.file_id,
-            range: TextRange::at(expr_range.range.end(), TextSize::of(';')),
-        })
-    } else {
-        Err(expr_range)
-    }
-}
-
-fn fixes(ctx: &DiagnosticsContext<'_>, d: &hir::RemoveThisSemicolon) -> Option<Vec<Assist>> {
-    let semicolon_range = semicolon_range(ctx, d).ok()?;
-
-    let edit = TextEdit::delete(semicolon_range.range);
-    let source_change = SourceChange::from_text_edit(semicolon_range.file_id, edit);
-
-    Some(vec![fix(
-        "remove_semicolon",
-        "Remove this semicolon",
-        source_change,
-        semicolon_range.range,
-    )])
-}
-
-#[cfg(test)]
-mod tests {
-    use crate::tests::{check_diagnostics, check_fix};
-
-    #[test]
-    fn missing_semicolon() {
-        check_diagnostics(
-            r#"
-fn test() -> i32 { 123; }
-                    //^ 💡 error: remove this semicolon
-"#,
-        );
-    }
-
-    #[test]
-    fn remove_semicolon() {
-        check_fix(r#"fn f() -> i32 { 92$0; }"#, r#"fn f() -> i32 { 92 }"#);
-    }
-}
diff --git a/crates/ide_diagnostics/src/handlers/type_mismatch.rs b/crates/ide_diagnostics/src/handlers/type_mismatch.rs
new file mode 100644
index 0000000000..040dcbd1d9
--- /dev/null
+++ b/crates/ide_diagnostics/src/handlers/type_mismatch.rs
@@ -0,0 +1,476 @@
+use hir::{db::AstDatabase, HirDisplay, Type, TypeInfo};
+use ide_db::{
+    famous_defs::FamousDefs, source_change::SourceChange,
+    syntax_helpers::node_ext::for_each_tail_expr,
+};
+use syntax::{
+    ast::{BlockExpr, ExprStmt},
+    AstNode,
+};
+use text_edit::TextEdit;
+
+use crate::{fix, Assist, Diagnostic, DiagnosticsContext};
+
+// Diagnostic: type-mismatch
+//
+// This diagnostic is triggered when the type of an expression does not match
+// the expected type.
+pub(crate) fn type_mismatch(ctx: &DiagnosticsContext<'_>, d: &hir::TypeMismatch) -> Diagnostic {
+    let mut diag = Diagnostic::new(
+        "type-mismatch",
+        format!(
+            "expected {}, found {}",
+            d.expected.display(ctx.sema.db),
+            d.actual.display(ctx.sema.db)
+        ),
+        ctx.sema.diagnostics_display_range(d.expr.clone().map(|it| it.into())).range,
+    )
+    .with_fixes(fixes(ctx, d));
+    if diag.fixes.is_none() {
+        diag.experimental = true;
+    }
+    diag
+}
+
+fn fixes(ctx: &DiagnosticsContext<'_>, d: &hir::TypeMismatch) -> Option<Vec<Assist>> {
+    let mut fixes = Vec::new();
+
+    add_reference(ctx, d, &mut fixes);
+    add_missing_ok_or_some(ctx, d, &mut fixes);
+    remove_semicolon(ctx, d, &mut fixes);
+
+    if fixes.is_empty() {
+        None
+    } else {
+        Some(fixes)
+    }
+}
+
+fn add_reference(
+    ctx: &DiagnosticsContext<'_>,
+    d: &hir::TypeMismatch,
+    acc: &mut Vec<Assist>,
+) -> Option<()> {
+    let root = ctx.sema.db.parse_or_expand(d.expr.file_id)?;
+    let expr_node = d.expr.value.to_node(&root);
+
+    let range = ctx.sema.diagnostics_display_range(d.expr.clone().map(|it| it.into())).range;
+
+    let (_, mutability) = d.expected.as_reference()?;
+    let actual_with_ref = Type::reference(&d.actual, mutability);
+    if !actual_with_ref.could_coerce_to(ctx.sema.db, &d.expected) {
+        return None;
+    }
+
+    let ampersands = format!("&{}", mutability.as_keyword_for_ref());
+
+    let edit = TextEdit::insert(expr_node.syntax().text_range().start(), ampersands);
+    let source_change =
+        SourceChange::from_text_edit(d.expr.file_id.original_file(ctx.sema.db), edit);
+    acc.push(fix("add_reference_here", "Add reference here", source_change, range));
+    Some(())
+}
+
+fn add_missing_ok_or_some(
+    ctx: &DiagnosticsContext<'_>,
+    d: &hir::TypeMismatch,
+    acc: &mut Vec<Assist>,
+) -> Option<()> {
+    let root = ctx.sema.db.parse_or_expand(d.expr.file_id)?;
+    let tail_expr = d.expr.value.to_node(&root);
+    let tail_expr_range = tail_expr.syntax().text_range();
+    let scope = ctx.sema.scope(tail_expr.syntax());
+
+    let expected_adt = d.expected.as_adt()?;
+    let expected_enum = expected_adt.as_enum()?;
+
+    let famous_defs = FamousDefs(&ctx.sema, scope.krate());
+    let core_result = famous_defs.core_result_Result();
+    let core_option = famous_defs.core_option_Option();
+
+    if Some(expected_enum) != core_result && Some(expected_enum) != core_option {
+        return None;
+    }
+
+    let variant_name = if Some(expected_enum) == core_result { "Ok" } else { "Some" };
+
+    let wrapped_actual_ty = expected_adt.ty_with_args(ctx.sema.db, &[d.actual.clone()]);
+
+    if !d.expected.could_unify_with(ctx.sema.db, &wrapped_actual_ty) {
+        return None;
+    }
+
+    let mut builder = TextEdit::builder();
+    for_each_tail_expr(&tail_expr, &mut |expr| {
+        if ctx.sema.type_of_expr(expr).map(TypeInfo::adjusted).as_ref() != Some(&d.expected) {
+            builder.insert(expr.syntax().text_range().start(), format!("{}(", variant_name));
+            builder.insert(expr.syntax().text_range().end(), ")".to_string());
+        }
+    });
+    let source_change =
+        SourceChange::from_text_edit(d.expr.file_id.original_file(ctx.sema.db), builder.finish());
+    let name = format!("Wrap in {}", variant_name);
+    acc.push(fix("wrap_tail_expr", &name, source_change, tail_expr_range));
+    Some(())
+}
+
+fn remove_semicolon(
+    ctx: &DiagnosticsContext<'_>,
+    d: &hir::TypeMismatch,
+    acc: &mut Vec<Assist>,
+) -> Option<()> {
+    let root = ctx.sema.db.parse_or_expand(d.expr.file_id)?;
+    let expr = d.expr.value.to_node(&root);
+    if !d.actual.is_unit() {
+        return None;
+    }
+    let block = BlockExpr::cast(expr.syntax().clone())?;
+    let expr_before_semi =
+        block.statements().last().and_then(|s| ExprStmt::cast(s.syntax().clone()))?;
+    let type_before_semi = ctx.sema.type_of_expr(&expr_before_semi.expr()?)?.original();
+    if !type_before_semi.could_coerce_to(ctx.sema.db, &d.expected) {
+        return None;
+    }
+    let semicolon_range = expr_before_semi.semicolon_token()?.text_range();
+
+    let edit = TextEdit::delete(semicolon_range);
+    let source_change =
+        SourceChange::from_text_edit(d.expr.file_id.original_file(ctx.sema.db), edit);
+
+    acc.push(fix("remove_semicolon", "Remove this semicolon", source_change, semicolon_range));
+    Some(())
+}
+
+#[cfg(test)]
+mod tests {
+    use crate::tests::{check_diagnostics, check_fix, check_no_fix};
+
+    #[test]
+    fn missing_reference() {
+        check_diagnostics(
+            r#"
+fn main() {
+    test(123);
+       //^^^ 💡 error: expected &i32, found i32
+}
+fn test(arg: &i32) {}
+"#,
+        );
+    }
+
+    #[test]
+    fn test_add_reference_to_int() {
+        check_fix(
+            r#"
+fn main() {
+    test(123$0);
+}
+fn test(arg: &i32) {}
+            "#,
+            r#"
+fn main() {
+    test(&123);
+}
+fn test(arg: &i32) {}
+            "#,
+        );
+    }
+
+    #[test]
+    fn test_add_mutable_reference_to_int() {
+        check_fix(
+            r#"
+fn main() {
+    test($0123);
+}
+fn test(arg: &mut i32) {}
+            "#,
+            r#"
+fn main() {
+    test(&mut 123);
+}
+fn test(arg: &mut i32) {}
+            "#,
+        );
+    }
+
+    #[test]
+    fn test_add_reference_to_array() {
+        check_fix(
+            r#"
+//- minicore: coerce_unsized
+fn main() {
+    test($0[1, 2, 3]);
+}
+fn test(arg: &[i32]) {}
+            "#,
+            r#"
+fn main() {
+    test(&[1, 2, 3]);
+}
+fn test(arg: &[i32]) {}
+            "#,
+        );
+    }
+
+    #[test]
+    fn test_add_reference_with_autoderef() {
+        check_fix(
+            r#"
+//- minicore: coerce_unsized, deref
+struct Foo;
+struct Bar;
+impl core::ops::Deref for Foo {
+    type Target = Bar;
+}
+
+fn main() {
+    test($0Foo);
+}
+fn test(arg: &Bar) {}
+            "#,
+            r#"
+struct Foo;
+struct Bar;
+impl core::ops::Deref for Foo {
+    type Target = Bar;
+}
+
+fn main() {
+    test(&Foo);
+}
+fn test(arg: &Bar) {}
+            "#,
+        );
+    }
+
+    #[test]
+    fn test_add_reference_to_method_call() {
+        check_fix(
+            r#"
+fn main() {
+    Test.call_by_ref($0123);
+}
+struct Test;
+impl Test {
+    fn call_by_ref(&self, arg: &i32) {}
+}
+            "#,
+            r#"
+fn main() {
+    Test.call_by_ref(&123);
+}
+struct Test;
+impl Test {
+    fn call_by_ref(&self, arg: &i32) {}
+}
+            "#,
+        );
+    }
+
+    #[test]
+    fn test_add_reference_to_let_stmt() {
+        check_fix(
+            r#"
+fn main() {
+    let test: &i32 = $0123;
+}
+            "#,
+            r#"
+fn main() {
+    let test: &i32 = &123;
+}
+            "#,
+        );
+    }
+
+    #[test]
+    fn test_add_mutable_reference_to_let_stmt() {
+        check_fix(
+            r#"
+fn main() {
+    let test: &mut i32 = $0123;
+}
+            "#,
+            r#"
+fn main() {
+    let test: &mut i32 = &mut 123;
+}
+            "#,
+        );
+    }
+
+    #[test]
+    fn test_wrap_return_type_option() {
+        check_fix(
+            r#"
+//- minicore: option, result
+fn div(x: i32, y: i32) -> Option<i32> {
+    if y == 0 {
+        return None;
+    }
+    x / y$0
+}
+"#,
+            r#"
+fn div(x: i32, y: i32) -> Option<i32> {
+    if y == 0 {
+        return None;
+    }
+    Some(x / y)
+}
+"#,
+        );
+    }
+
+    #[test]
+    fn test_wrap_return_type_option_tails() {
+        check_fix(
+            r#"
+//- minicore: option, result
+fn div(x: i32, y: i32) -> Option<i32> {
+    if y == 0 {
+        0
+    } else if true {
+        100
+    } else {
+        None
+    }$0
+}
+"#,
+            r#"
+fn div(x: i32, y: i32) -> Option<i32> {
+    if y == 0 {
+        Some(0)
+    } else if true {
+        Some(100)
+    } else {
+        None
+    }
+}
+"#,
+        );
+    }
+
+    #[test]
+    fn test_wrap_return_type() {
+        check_fix(
+            r#"
+//- minicore: option, result
+fn div(x: i32, y: i32) -> Result<i32, ()> {
+    if y == 0 {
+        return Err(());
+    }
+    x / y$0
+}
+"#,
+            r#"
+fn div(x: i32, y: i32) -> Result<i32, ()> {
+    if y == 0 {
+        return Err(());
+    }
+    Ok(x / y)
+}
+"#,
+        );
+    }
+
+    #[test]
+    fn test_wrap_return_type_handles_generic_functions() {
+        check_fix(
+            r#"
+//- minicore: option, result
+fn div<T>(x: T) -> Result<T, i32> {
+    if x == 0 {
+        return Err(7);
+    }
+    $0x
+}
+"#,
+            r#"
+fn div<T>(x: T) -> Result<T, i32> {
+    if x == 0 {
+        return Err(7);
+    }
+    Ok(x)
+}
+"#,
+        );
+    }
+
+    #[test]
+    fn test_wrap_return_type_handles_type_aliases() {
+        check_fix(
+            r#"
+//- minicore: option, result
+type MyResult<T> = Result<T, ()>;
+
+fn div(x: i32, y: i32) -> MyResult<i32> {
+    if y == 0 {
+        return Err(());
+    }
+    x $0/ y
+}
+"#,
+            r#"
+type MyResult<T> = Result<T, ()>;
+
+fn div(x: i32, y: i32) -> MyResult<i32> {
+    if y == 0 {
+        return Err(());
+    }
+    Ok(x / y)
+}
+"#,
+        );
+    }
+
+    #[test]
+    fn test_in_const_and_static() {
+        check_fix(
+            r#"
+//- minicore: option, result
+static A: Option<()> = {($0)};
+            "#,
+            r#"
+static A: Option<()> = {Some(())};
+            "#,
+        );
+        check_fix(
+            r#"
+//- minicore: option, result
+const _: Option<()> = {($0)};
+            "#,
+            r#"
+const _: Option<()> = {Some(())};
+            "#,
+        );
+    }
+
+    #[test]
+    fn test_wrap_return_type_not_applicable_when_expr_type_does_not_match_ok_type() {
+        check_no_fix(
+            r#"
+//- minicore: option, result
+fn foo() -> Result<(), i32> { 0$0 }
+"#,
+        );
+    }
+
+    #[test]
+    fn test_wrap_return_type_not_applicable_when_return_type_is_not_result_or_option() {
+        check_no_fix(
+            r#"
+//- minicore: option, result
+enum SomeOtherEnum { Ok(i32), Err(String) }
+
+fn foo() -> SomeOtherEnum { 0$0 }
+"#,
+        );
+    }
+
+    #[test]
+    fn remove_semicolon() {
+        check_fix(r#"fn f() -> i32 { 92$0; }"#, r#"fn f() -> i32 { 92 }"#);
+    }
+}
diff --git a/crates/ide_diagnostics/src/lib.rs b/crates/ide_diagnostics/src/lib.rs
index 86d76751ad..13ac0a2450 100644
--- a/crates/ide_diagnostics/src/lib.rs
+++ b/crates/ide_diagnostics/src/lib.rs
@@ -24,7 +24,6 @@
 //! don't yet have a great pattern for how to do them properly.
 
 mod handlers {
-    pub(crate) mod add_reference_here;
     pub(crate) mod break_outside_of_loop;
     pub(crate) mod inactive_code;
     pub(crate) mod incorrect_case;
@@ -34,11 +33,10 @@ mod handlers {
     pub(crate) mod mismatched_arg_count;
     pub(crate) mod missing_fields;
     pub(crate) mod missing_match_arms;
-    pub(crate) mod missing_ok_or_some_in_tail_expr;
     pub(crate) mod missing_unsafe;
     pub(crate) mod no_such_field;
-    pub(crate) mod remove_this_semicolon;
     pub(crate) mod replace_filter_map_next_with_find_map;
+    pub(crate) mod type_mismatch;
     pub(crate) mod unimplemented_builtin_macro;
     pub(crate) mod unresolved_extern_crate;
     pub(crate) mod unresolved_import;
@@ -191,7 +189,6 @@ pub fn diagnostics(
     for diag in diags {
         #[rustfmt::skip]
         let d = match diag {
-            AnyDiagnostic::AddReferenceHere(d) => handlers::add_reference_here::add_reference_here(&ctx, &d),
             AnyDiagnostic::BreakOutsideOfLoop(d) => handlers::break_outside_of_loop::break_outside_of_loop(&ctx, &d),
             AnyDiagnostic::IncorrectCase(d) => handlers::incorrect_case::incorrect_case(&ctx, &d),
             AnyDiagnostic::MacroError(d) => handlers::macro_error::macro_error(&ctx, &d),
@@ -199,11 +196,10 @@ pub fn diagnostics(
             AnyDiagnostic::MismatchedArgCount(d) => handlers::mismatched_arg_count::mismatched_arg_count(&ctx, &d),
             AnyDiagnostic::MissingFields(d) => handlers::missing_fields::missing_fields(&ctx, &d),
             AnyDiagnostic::MissingMatchArms(d) => handlers::missing_match_arms::missing_match_arms(&ctx, &d),
-            AnyDiagnostic::MissingOkOrSomeInTailExpr(d) => handlers::missing_ok_or_some_in_tail_expr::missing_ok_or_some_in_tail_expr(&ctx, &d),
             AnyDiagnostic::MissingUnsafe(d) => handlers::missing_unsafe::missing_unsafe(&ctx, &d),
             AnyDiagnostic::NoSuchField(d) => handlers::no_such_field::no_such_field(&ctx, &d),
-            AnyDiagnostic::RemoveThisSemicolon(d) => handlers::remove_this_semicolon::remove_this_semicolon(&ctx, &d),
             AnyDiagnostic::ReplaceFilterMapNextWithFindMap(d) => handlers::replace_filter_map_next_with_find_map::replace_filter_map_next_with_find_map(&ctx, &d),
+            AnyDiagnostic::TypeMismatch(d) => handlers::type_mismatch::type_mismatch(&ctx, &d),
             AnyDiagnostic::UnimplementedBuiltinMacro(d) => handlers::unimplemented_builtin_macro::unimplemented_builtin_macro(&ctx, &d),
             AnyDiagnostic::UnresolvedExternCrate(d) => handlers::unresolved_extern_crate::unresolved_extern_crate(&ctx, &d),
             AnyDiagnostic::UnresolvedImport(d) => handlers::unresolved_import::unresolved_import(&ctx, &d),


Test patch:
diff --git a/crates/hir_ty/src/tests/simple.rs b/crates/hir_ty/src/tests/simple.rs
index 0d050f7461..de27c294f6 100644
--- a/crates/hir_ty/src/tests/simple.rs
+++ b/crates/hir_ty/src/tests/simple.rs
@@ -1,6 +1,6 @@
 use expect_test::expect;
 
-use super::{check_infer, check_types};
+use super::{check_infer, check_no_mismatches, check_types};
 
 #[test]
 fn infer_box() {
@@ -2624,3 +2624,21 @@ pub mod prelude {
     "#,
     );
 }
+
+#[test]
+fn legacy_const_generics() {
+    check_no_mismatches(
+        r#"
+#[rustc_legacy_const_generics(1, 3)]
+fn mixed<const N1: &'static str, const N2: bool>(
+    a: u8,
+    b: i8,
+) {}
+
+fn f() {
+    mixed(0, "", -1, true);
+    mixed::<"", true>(0, -1);
+}
+    "#,
+    );
+}