about summary refs log tree commit diff
diff options
context:
space:
mode:
-rw-r--r--Cargo.lock48
-rw-r--r--crates/hir-expand/src/builtin_fn_macro.rs2
-rw-r--r--crates/hir-ty/Cargo.toml1
-rw-r--r--crates/hir-ty/src/infer.rs2
-rw-r--r--crates/hir-ty/src/infer/expr.rs37
-rw-r--r--crates/hir-ty/src/infer/unify.rs85
-rw-r--r--crates/hir-ty/src/tests.rs27
-rw-r--r--crates/hir-ty/src/tests/coercion.rs34
-rw-r--r--crates/hir-ty/src/tests/traits.rs65
-rw-r--r--crates/ide-assists/src/handlers/add_explicit_type.rs5
-rw-r--r--crates/ide-assists/src/handlers/extract_type_alias.rs120
-rw-r--r--crates/ide-completion/src/completions/record.rs31
-rw-r--r--crates/ide-completion/src/render/variant.rs3
-rw-r--r--crates/ide-db/src/syntax_helpers/node_ext.rs9
-rw-r--r--crates/ide/src/inlay_hints.rs4
-rw-r--r--crates/ide/src/inlay_hints/discriminant.rs (renamed from crates/ide/src/inlay_hints/discrimant.rs)0
-rw-r--r--crates/ide/src/inlay_hints/fn_lifetime_fn.rs30
-rw-r--r--crates/rust-analyzer/src/diagnostics/to_proto.rs5
-rw-r--r--crates/rust-analyzer/tests/slow-tests/tidy.rs1
-rw-r--r--crates/test-utils/src/minicore.rs6
-rw-r--r--editors/code/package.json5
-rw-r--r--editors/code/src/client.ts32
-rw-r--r--editors/code/src/config.ts4
-rw-r--r--xtask/Cargo.toml1
-rw-r--r--xtask/src/dist.rs38
25 files changed, 488 insertions, 107 deletions
diff --git a/Cargo.lock b/Cargo.lock
index 4c255b12b7f..d27ae416f04 100644
--- a/Cargo.lock
+++ b/Cargo.lock
@@ -113,6 +113,12 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a"
 
 [[package]]
+name = "byteorder"
+version = "1.4.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "14c189c53d098945499cdfa7ecc63567cf3886b3332b312a5b4585d8d3a6a610"
+
+[[package]]
 name = "camino"
 version = "1.1.1"
 source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -557,6 +563,7 @@ version = "0.0.0"
 dependencies = [
  "arrayvec",
  "base-db",
+ "bitflags",
  "chalk-derive",
  "chalk-ir",
  "chalk-recursive",
@@ -1776,6 +1783,33 @@ dependencies = [
 ]
 
 [[package]]
+name = "time"
+version = "0.3.17"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "a561bf4617eebd33bca6434b988f39ed798e527f51a1e797d0ee4f61c0a38376"
+dependencies = [
+ "itoa",
+ "serde",
+ "time-core",
+ "time-macros",
+]
+
+[[package]]
+name = "time-core"
+version = "0.1.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "2e153e1f1acaef8acc537e68b44906d2db6436e2b35ac2c6b42640fff91f00fd"
+
+[[package]]
+name = "time-macros"
+version = "0.2.6"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "d967f99f534ca7e495c575c62638eebc2898a8c84c119b89e250477bc4ba16b2"
+dependencies = [
+ "time-core",
+]
+
+[[package]]
 name = "tinyvec"
 version = "1.6.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -2173,4 +2207,18 @@ dependencies = [
  "write-json",
  "xflags",
  "xshell",
+ "zip",
+]
+
+[[package]]
+name = "zip"
+version = "0.6.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "537ce7411d25e54e8ae21a7ce0b15840e7bfcff15b51d697ec3266cc76bdf080"
+dependencies = [
+ "byteorder",
+ "crc32fast",
+ "crossbeam-utils",
+ "flate2",
+ "time",
 ]
diff --git a/crates/hir-expand/src/builtin_fn_macro.rs b/crates/hir-expand/src/builtin_fn_macro.rs
index e5f1a5ebb20..5522bdf3b3f 100644
--- a/crates/hir-expand/src/builtin_fn_macro.rs
+++ b/crates/hir-expand/src/builtin_fn_macro.rs
@@ -671,7 +671,7 @@ fn option_env_expand(
 
     let expanded = match get_env_inner(db, arg_id, &key) {
         None => quote! { #DOLLAR_CRATE::option::Option::None::<&str> },
-        Some(s) => quote! { #DOLLAR_CRATE::option::Some(#s) },
+        Some(s) => quote! { #DOLLAR_CRATE::option::Option::Some(#s) },
     };
 
     ExpandResult::ok(ExpandedEager::new(expanded))
diff --git a/crates/hir-ty/Cargo.toml b/crates/hir-ty/Cargo.toml
index c72199c37fe..ae837ac6dce 100644
--- a/crates/hir-ty/Cargo.toml
+++ b/crates/hir-ty/Cargo.toml
@@ -13,6 +13,7 @@ doctest = false
 cov-mark = "2.0.0-pre.1"
 itertools = "0.10.5"
 arrayvec = "0.7.2"
+bitflags = "1.3.2"
 smallvec = "1.10.0"
 ena = "0.14.0"
 tracing = "0.1.35"
diff --git a/crates/hir-ty/src/infer.rs b/crates/hir-ty/src/infer.rs
index 7b54886d53f..6b59f1c20da 100644
--- a/crates/hir-ty/src/infer.rs
+++ b/crates/hir-ty/src/infer.rs
@@ -512,6 +512,8 @@ impl<'a> InferenceContext<'a> {
     fn resolve_all(self) -> InferenceResult {
         let InferenceContext { mut table, mut result, .. } = self;
 
+        table.fallback_if_possible();
+
         // FIXME resolve obligations as well (use Guidance if necessary)
         table.resolve_obligations_as_possible();
 
diff --git a/crates/hir-ty/src/infer/expr.rs b/crates/hir-ty/src/infer/expr.rs
index ba8f55e8ae3..8f9cdac3784 100644
--- a/crates/hir-ty/src/infer/expr.rs
+++ b/crates/hir-ty/src/infer/expr.rs
@@ -6,7 +6,7 @@ use std::{
 };
 
 use chalk_ir::{
-    cast::Cast, fold::Shift, DebruijnIndex, GenericArgData, Mutability, TyVariableKind,
+    cast::Cast, fold::Shift, DebruijnIndex, GenericArgData, Mutability, TyKind, TyVariableKind,
 };
 use hir_def::{
     expr::{
@@ -34,8 +34,8 @@ use crate::{
     primitive::{self, UintTy},
     static_lifetime, to_chalk_trait_id,
     utils::{generics, Generics},
-    AdtId, Binders, CallableDefId, FnPointer, FnSig, FnSubst, Interner, Rawness, Scalar,
-    Substitution, TraitRef, Ty, TyBuilder, TyExt, TyKind,
+    Adjust, Adjustment, AdtId, AutoBorrow, Binders, CallableDefId, FnPointer, FnSig, FnSubst,
+    Interner, Rawness, Scalar, Substitution, TraitRef, Ty, TyBuilder, TyExt,
 };
 
 use super::{
@@ -334,6 +334,7 @@ impl<'a> InferenceContext<'a> {
                 let (param_tys, ret_ty) = match res {
                     Some(res) => {
                         let adjustments = auto_deref_adjust_steps(&derefs);
+                        // FIXME: Handle call adjustments for Fn/FnMut
                         self.write_expr_adj(*callee, adjustments);
                         res
                     }
@@ -1038,14 +1039,38 @@ impl<'a> InferenceContext<'a> {
         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(),
+            Some(sig) => {
+                let p_left = &sig.params()[0];
+                if matches!(op, BinaryOp::CmpOp(..) | BinaryOp::Assignment { .. }) {
+                    if let &TyKind::Ref(mtbl, _, _) = p_left.kind(Interner) {
+                        self.write_expr_adj(
+                            lhs,
+                            vec![Adjustment {
+                                kind: Adjust::Borrow(AutoBorrow::Ref(mtbl)),
+                                target: p_left.clone(),
+                            }],
+                        );
+                    }
+                }
+                let p_right = &sig.params()[1];
+                if matches!(op, BinaryOp::CmpOp(..)) {
+                    if let &TyKind::Ref(mtbl, _, _) = p_right.kind(Interner) {
+                        self.write_expr_adj(
+                            rhs,
+                            vec![Adjustment {
+                                kind: Adjust::Borrow(AutoBorrow::Ref(mtbl)),
+                                target: p_right.clone(),
+                            }],
+                        );
+                    }
+                }
+                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);
diff --git a/crates/hir-ty/src/infer/unify.rs b/crates/hir-ty/src/infer/unify.rs
index 12f45f00f9c..e7ddd1591fe 100644
--- a/crates/hir-ty/src/infer/unify.rs
+++ b/crates/hir-ty/src/infer/unify.rs
@@ -1,6 +1,6 @@
 //! Unification and canonicalization logic.
 
-use std::{fmt, mem, sync::Arc};
+use std::{fmt, iter, mem, sync::Arc};
 
 use chalk_ir::{
     cast::Cast, fold::TypeFoldable, interner::HasInterner, zip::Zip, CanonicalVarKind, FloatTy,
@@ -128,9 +128,13 @@ pub(crate) fn unify(
     ))
 }
 
-#[derive(Copy, Clone, Debug)]
-pub(crate) struct TypeVariableData {
-    diverging: bool,
+bitflags::bitflags! {
+    #[derive(Default)]
+    pub(crate) struct TypeVariableFlags: u8 {
+        const DIVERGING = 1 << 0;
+        const INTEGER = 1 << 1;
+        const FLOAT = 1 << 2;
+    }
 }
 
 type ChalkInferenceTable = chalk_solve::infer::InferenceTable<Interner>;
@@ -140,14 +144,14 @@ pub(crate) struct InferenceTable<'a> {
     pub(crate) db: &'a dyn HirDatabase,
     pub(crate) trait_env: Arc<TraitEnvironment>,
     var_unification_table: ChalkInferenceTable,
-    type_variable_table: Vec<TypeVariableData>,
+    type_variable_table: Vec<TypeVariableFlags>,
     pending_obligations: Vec<Canonicalized<InEnvironment<Goal>>>,
 }
 
 pub(crate) struct InferenceTableSnapshot {
     var_table_snapshot: chalk_solve::infer::InferenceSnapshot<Interner>,
     pending_obligations: Vec<Canonicalized<InEnvironment<Goal>>>,
-    type_variable_table_snapshot: Vec<TypeVariableData>,
+    type_variable_table_snapshot: Vec<TypeVariableFlags>,
 }
 
 impl<'a> InferenceTable<'a> {
@@ -169,19 +173,19 @@ impl<'a> InferenceTable<'a> {
     /// result.
     pub(super) fn propagate_diverging_flag(&mut self) {
         for i in 0..self.type_variable_table.len() {
-            if !self.type_variable_table[i].diverging {
+            if !self.type_variable_table[i].contains(TypeVariableFlags::DIVERGING) {
                 continue;
             }
             let v = InferenceVar::from(i as u32);
             let root = self.var_unification_table.inference_var_root(v);
             if let Some(data) = self.type_variable_table.get_mut(root.index() as usize) {
-                data.diverging = true;
+                *data |= TypeVariableFlags::DIVERGING;
             }
         }
     }
 
     pub(super) fn set_diverging(&mut self, iv: InferenceVar, diverging: bool) {
-        self.type_variable_table[iv.index() as usize].diverging = diverging;
+        self.type_variable_table[iv.index() as usize].set(TypeVariableFlags::DIVERGING, diverging);
     }
 
     fn fallback_value(&self, iv: InferenceVar, kind: TyVariableKind) -> Ty {
@@ -189,7 +193,7 @@ impl<'a> InferenceTable<'a> {
             _ if self
                 .type_variable_table
                 .get(iv.index() as usize)
-                .map_or(false, |data| data.diverging) =>
+                .map_or(false, |data| data.contains(TypeVariableFlags::DIVERGING)) =>
             {
                 TyKind::Never
             }
@@ -247,10 +251,8 @@ impl<'a> InferenceTable<'a> {
     }
 
     fn extend_type_variable_table(&mut self, to_index: usize) {
-        self.type_variable_table.extend(
-            (0..1 + to_index - self.type_variable_table.len())
-                .map(|_| TypeVariableData { diverging: false }),
-        );
+        let count = to_index - self.type_variable_table.len() + 1;
+        self.type_variable_table.extend(iter::repeat(TypeVariableFlags::default()).take(count));
     }
 
     fn new_var(&mut self, kind: TyVariableKind, diverging: bool) -> Ty {
@@ -258,7 +260,15 @@ impl<'a> InferenceTable<'a> {
         // Chalk might have created some type variables for its own purposes that we don't know about...
         self.extend_type_variable_table(var.index() as usize);
         assert_eq!(var.index() as usize, self.type_variable_table.len() - 1);
-        self.type_variable_table[var.index() as usize].diverging = diverging;
+        let flags = self.type_variable_table.get_mut(var.index() as usize).unwrap();
+        if diverging {
+            *flags |= TypeVariableFlags::DIVERGING;
+        }
+        if matches!(kind, TyVariableKind::Integer) {
+            *flags |= TypeVariableFlags::INTEGER;
+        } else if matches!(kind, TyVariableKind::Float) {
+            *flags |= TypeVariableFlags::FLOAT;
+        }
         var.to_ty_with_kind(Interner, kind)
     }
 
@@ -340,6 +350,51 @@ impl<'a> InferenceTable<'a> {
         self.resolve_with_fallback(t, &|_, _, d, _| d)
     }
 
+    /// Apply a fallback to unresolved scalar types. Integer type variables and float type
+    /// variables are replaced with i32 and f64, respectively.
+    ///
+    /// This method is only intended to be called just before returning inference results (i.e. in
+    /// `InferenceContext::resolve_all()`).
+    ///
+    /// FIXME: This method currently doesn't apply fallback to unconstrained general type variables
+    /// whereas rustc replaces them with `()` or `!`.
+    pub(super) fn fallback_if_possible(&mut self) {
+        let int_fallback = TyKind::Scalar(Scalar::Int(IntTy::I32)).intern(Interner);
+        let float_fallback = TyKind::Scalar(Scalar::Float(FloatTy::F64)).intern(Interner);
+
+        let scalar_vars: Vec<_> = self
+            .type_variable_table
+            .iter()
+            .enumerate()
+            .filter_map(|(index, flags)| {
+                let kind = if flags.contains(TypeVariableFlags::INTEGER) {
+                    TyVariableKind::Integer
+                } else if flags.contains(TypeVariableFlags::FLOAT) {
+                    TyVariableKind::Float
+                } else {
+                    return None;
+                };
+
+                // FIXME: This is not really the nicest way to get `InferenceVar`s. Can we get them
+                // without directly constructing them from `index`?
+                let var = InferenceVar::from(index as u32).to_ty(Interner, kind);
+                Some(var)
+            })
+            .collect();
+
+        for var in scalar_vars {
+            let maybe_resolved = self.resolve_ty_shallow(&var);
+            if let TyKind::InferenceVar(_, kind) = maybe_resolved.kind(Interner) {
+                let fallback = match kind {
+                    TyVariableKind::Integer => &int_fallback,
+                    TyVariableKind::Float => &float_fallback,
+                    TyVariableKind::General => unreachable!(),
+                };
+                self.unify(&var, fallback);
+            }
+        }
+    }
+
     /// Unify two relatable values (e.g. `Ty`) and register new trait goals that arise from that.
     pub(crate) fn unify<T: ?Sized + Zip<Interner>>(&mut self, ty1: &T, ty2: &T) -> bool {
         let result = match self.try_unify(ty1, ty2) {
diff --git a/crates/hir-ty/src/tests.rs b/crates/hir-ty/src/tests.rs
index 7bcf89ff59c..ba5d9c24126 100644
--- a/crates/hir-ty/src/tests.rs
+++ b/crates/hir-ty/src/tests.rs
@@ -94,11 +94,12 @@ fn check_impl(ra_fixture: &str, allow_none: bool, only_types: bool, display_sour
                 types.insert(file_range, expected.trim_start_matches("type: ").to_string());
             } else if expected.starts_with("expected") {
                 mismatches.insert(file_range, expected);
-            } else if expected.starts_with("adjustments: ") {
+            } else if expected.starts_with("adjustments:") {
                 adjustments.insert(
                     file_range,
                     expected
-                        .trim_start_matches("adjustments: ")
+                        .trim_start_matches("adjustments:")
+                        .trim()
                         .split(',')
                         .map(|it| it.trim().to_string())
                         .filter(|it| !it.is_empty())
@@ -176,17 +177,17 @@ fn check_impl(ra_fixture: &str, allow_none: bool, only_types: bool, display_sour
                 assert_eq!(actual, expected);
             }
             if let Some(expected) = adjustments.remove(&range) {
-                if let Some(adjustments) = inference_result.expr_adjustments.get(&expr) {
-                    assert_eq!(
-                        expected,
-                        adjustments
-                            .iter()
-                            .map(|Adjustment { kind, .. }| format!("{kind:?}"))
-                            .collect::<Vec<_>>()
-                    );
-                } else {
-                    panic!("expected {expected:?} adjustments, found none");
-                }
+                let adjustments = inference_result
+                    .expr_adjustments
+                    .get(&expr)
+                    .map_or_else(Default::default, |it| &**it);
+                assert_eq!(
+                    expected,
+                    adjustments
+                        .iter()
+                        .map(|Adjustment { kind, .. }| format!("{kind:?}"))
+                        .collect::<Vec<_>>()
+                );
             }
         }
 
diff --git a/crates/hir-ty/src/tests/coercion.rs b/crates/hir-ty/src/tests/coercion.rs
index 7e3aecc2ae0..3e110abaf4b 100644
--- a/crates/hir-ty/src/tests/coercion.rs
+++ b/crates/hir-ty/src/tests/coercion.rs
@@ -807,3 +807,37 @@ fn main() {
         "#,
     );
 }
+
+#[test]
+fn adjust_comparison_arguments() {
+    check_no_mismatches(
+        r"
+//- minicore: eq
+struct Struct;
+impl core::cmp::PartialEq for Struct {
+    fn eq(&self, other: &Self) -> bool { true }
+}
+fn test() {
+    Struct == Struct;
+ // ^^^^^^ adjustments: Borrow(Ref(Not))
+           // ^^^^^^ adjustments: Borrow(Ref(Not))
+}",
+    );
+}
+
+#[test]
+fn adjust_assign_lhs() {
+    check_no_mismatches(
+        r"
+//- minicore: add
+struct Struct;
+impl core::ops::AddAssign for Struct {
+    fn add_assign(&mut self, other: Self) {}
+}
+fn test() {
+    Struct += Struct;
+ // ^^^^^^ adjustments: Borrow(Ref(Mut))
+           // ^^^^^^ adjustments:
+}",
+    );
+}
diff --git a/crates/hir-ty/src/tests/traits.rs b/crates/hir-ty/src/tests/traits.rs
index a9fd01ee011..d01fe063285 100644
--- a/crates/hir-ty/src/tests/traits.rs
+++ b/crates/hir-ty/src/tests/traits.rs
@@ -4100,3 +4100,68 @@ where
     "#,
     );
 }
+
+#[test]
+fn bin_op_with_scalar_fallback() {
+    // Extra impls are significant so that chalk doesn't give us definite guidances.
+    check_types(
+        r#"
+//- minicore: add
+use core::ops::Add;
+
+struct Vec2<T>(T, T);
+
+impl Add for Vec2<i32> {
+    type Output = Self;
+    fn add(self, rhs: Self) -> Self::Output { loop {} }
+}
+impl Add for Vec2<u32> {
+    type Output = Self;
+    fn add(self, rhs: Self) -> Self::Output { loop {} }
+}
+impl Add for Vec2<f32> {
+    type Output = Self;
+    fn add(self, rhs: Self) -> Self::Output { loop {} }
+}
+impl Add for Vec2<f64> {
+    type Output = Self;
+    fn add(self, rhs: Self) -> Self::Output { loop {} }
+}
+
+fn test() {
+    let a = Vec2(1, 2);
+    let b = Vec2(3, 4);
+    let c = a + b;
+      //^ Vec2<i32>
+    let a = Vec2(1., 2.);
+    let b = Vec2(3., 4.);
+    let c = a + b;
+      //^ Vec2<f64>
+}
+"#,
+    );
+}
+
+#[test]
+fn trait_method_with_scalar_fallback() {
+    check_types(
+        r#"
+trait Trait {
+    type Output;
+    fn foo(&self) -> Self::Output;
+}
+impl<T> Trait for T {
+    type Output = T;
+    fn foo(&self) -> Self::Output { loop {} }
+}
+fn test() {
+    let a = 42;
+    let b = a.foo();
+      //^ i32
+    let a = 3.14;
+    let b = a.foo();
+      //^ f64
+}
+"#,
+    );
+}
diff --git a/crates/ide-assists/src/handlers/add_explicit_type.rs b/crates/ide-assists/src/handlers/add_explicit_type.rs
index b5f99726fe1..0057f439f1a 100644
--- a/crates/ide-assists/src/handlers/add_explicit_type.rs
+++ b/crates/ide-assists/src/handlers/add_explicit_type.rs
@@ -47,7 +47,10 @@ pub(crate) fn add_explicit_type(acc: &mut Assists, ctx: &AssistContext<'_>) -> O
     // Don't enable the assist if there is a type ascription without any placeholders
     if let Some(ty) = &ascribed_ty {
         let mut contains_infer_ty = false;
-        walk_ty(ty, &mut |ty| contains_infer_ty |= matches!(ty, ast::Type::InferType(_)));
+        walk_ty(ty, &mut |ty| {
+            contains_infer_ty |= matches!(ty, ast::Type::InferType(_));
+            false
+        });
         if !contains_infer_ty {
             cov_mark::hit!(add_explicit_type_not_applicable_if_ty_already_specified);
             return None;
diff --git a/crates/ide-assists/src/handlers/extract_type_alias.rs b/crates/ide-assists/src/handlers/extract_type_alias.rs
index 3116935fc5e..0505f5784f8 100644
--- a/crates/ide-assists/src/handlers/extract_type_alias.rs
+++ b/crates/ide-assists/src/handlers/extract_type_alias.rs
@@ -108,76 +108,80 @@ fn collect_used_generics<'gp>(
     }
 
     let mut generics = Vec::new();
-    walk_ty(ty, &mut |ty| match ty {
-        ast::Type::PathType(ty) => {
-            if let Some(path) = ty.path() {
-                if let Some(name_ref) = path.as_single_name_ref() {
-                    if let Some(param) = known_generics.iter().find(|gp| {
-                        match gp {
-                            ast::GenericParam::ConstParam(cp) => cp.name(),
-                            ast::GenericParam::TypeParam(tp) => tp.name(),
-                            _ => None,
+    walk_ty(ty, &mut |ty| {
+        match ty {
+            ast::Type::PathType(ty) => {
+                if let Some(path) = ty.path() {
+                    if let Some(name_ref) = path.as_single_name_ref() {
+                        if let Some(param) = known_generics.iter().find(|gp| {
+                            match gp {
+                                ast::GenericParam::ConstParam(cp) => cp.name(),
+                                ast::GenericParam::TypeParam(tp) => tp.name(),
+                                _ => None,
+                            }
+                            .map_or(false, |n| n.text() == name_ref.text())
+                        }) {
+                            generics.push(param);
                         }
-                        .map_or(false, |n| n.text() == name_ref.text())
-                    }) {
-                        generics.push(param);
                     }
+                    generics.extend(
+                        path.segments()
+                            .filter_map(|seg| seg.generic_arg_list())
+                            .flat_map(|it| it.generic_args())
+                            .filter_map(|it| match it {
+                                ast::GenericArg::LifetimeArg(lt) => {
+                                    let lt = lt.lifetime()?;
+                                    known_generics.iter().find(find_lifetime(&lt.text()))
+                                }
+                                _ => None,
+                            }),
+                    );
                 }
-                generics.extend(
-                    path.segments()
-                        .filter_map(|seg| seg.generic_arg_list())
-                        .flat_map(|it| it.generic_args())
-                        .filter_map(|it| match it {
-                            ast::GenericArg::LifetimeArg(lt) => {
-                                let lt = lt.lifetime()?;
-                                known_generics.iter().find(find_lifetime(&lt.text()))
-                            }
-                            _ => None,
-                        }),
-                );
             }
-        }
-        ast::Type::ImplTraitType(impl_ty) => {
-            if let Some(it) = impl_ty.type_bound_list() {
-                generics.extend(
-                    it.bounds()
-                        .filter_map(|it| it.lifetime())
-                        .filter_map(|lt| known_generics.iter().find(find_lifetime(&lt.text()))),
-                );
+            ast::Type::ImplTraitType(impl_ty) => {
+                if let Some(it) = impl_ty.type_bound_list() {
+                    generics.extend(
+                        it.bounds()
+                            .filter_map(|it| it.lifetime())
+                            .filter_map(|lt| known_generics.iter().find(find_lifetime(&lt.text()))),
+                    );
+                }
             }
-        }
-        ast::Type::DynTraitType(dyn_ty) => {
-            if let Some(it) = dyn_ty.type_bound_list() {
-                generics.extend(
-                    it.bounds()
-                        .filter_map(|it| it.lifetime())
-                        .filter_map(|lt| known_generics.iter().find(find_lifetime(&lt.text()))),
-                );
+            ast::Type::DynTraitType(dyn_ty) => {
+                if let Some(it) = dyn_ty.type_bound_list() {
+                    generics.extend(
+                        it.bounds()
+                            .filter_map(|it| it.lifetime())
+                            .filter_map(|lt| known_generics.iter().find(find_lifetime(&lt.text()))),
+                    );
+                }
             }
-        }
-        ast::Type::RefType(ref_) => generics.extend(
-            ref_.lifetime().and_then(|lt| known_generics.iter().find(find_lifetime(&lt.text()))),
-        ),
-        ast::Type::ArrayType(ar) => {
-            if let Some(expr) = ar.expr() {
-                if let ast::Expr::PathExpr(p) = expr {
-                    if let Some(path) = p.path() {
-                        if let Some(name_ref) = path.as_single_name_ref() {
-                            if let Some(param) = known_generics.iter().find(|gp| {
-                                if let ast::GenericParam::ConstParam(cp) = gp {
-                                    cp.name().map_or(false, |n| n.text() == name_ref.text())
-                                } else {
-                                    false
+            ast::Type::RefType(ref_) => generics.extend(
+                ref_.lifetime()
+                    .and_then(|lt| known_generics.iter().find(find_lifetime(&lt.text()))),
+            ),
+            ast::Type::ArrayType(ar) => {
+                if let Some(expr) = ar.expr() {
+                    if let ast::Expr::PathExpr(p) = expr {
+                        if let Some(path) = p.path() {
+                            if let Some(name_ref) = path.as_single_name_ref() {
+                                if let Some(param) = known_generics.iter().find(|gp| {
+                                    if let ast::GenericParam::ConstParam(cp) = gp {
+                                        cp.name().map_or(false, |n| n.text() == name_ref.text())
+                                    } else {
+                                        false
+                                    }
+                                }) {
+                                    generics.push(param);
                                 }
-                            }) {
-                                generics.push(param);
                             }
                         }
                     }
                 }
             }
-        }
-        _ => (),
+            _ => (),
+        };
+        false
     });
     // stable resort to lifetime, type, const
     generics.sort_by_key(|gp| match gp {
diff --git a/crates/ide-completion/src/completions/record.rs b/crates/ide-completion/src/completions/record.rs
index 6743ec897f0..0521e735ded 100644
--- a/crates/ide-completion/src/completions/record.rs
+++ b/crates/ide-completion/src/completions/record.rs
@@ -159,8 +159,9 @@ fn baz() {
     #[test]
     fn enum_variant_no_snippets() {
         let conf = CompletionConfig { snippet_cap: SnippetCap::new(false), ..TEST_CONFIG };
+        // tuple variant
         check_edit_with_config(
-            conf,
+            conf.clone(),
             "Variant()",
             r#"
 enum Enum {
@@ -184,6 +185,34 @@ impl Enum {
     }
 }
 "#,
+        );
+
+        // record variant
+        check_edit_with_config(
+            conf,
+            "Variant{}",
+            r#"
+enum Enum {
+    Variant{u: usize},
+}
+
+impl Enum {
+    fn new(u: usize) -> Self {
+        Self::Va$0
+    }
+}
+"#,
+            r#"
+enum Enum {
+    Variant{u: usize},
+}
+
+impl Enum {
+    fn new(u: usize) -> Self {
+        Self::Variant
+    }
+}
+"#,
         )
     }
 
diff --git a/crates/ide-completion/src/render/variant.rs b/crates/ide-completion/src/render/variant.rs
index f3e88489f46..55c55725be4 100644
--- a/crates/ide-completion/src/render/variant.rs
+++ b/crates/ide-completion/src/render/variant.rs
@@ -22,6 +22,9 @@ pub(crate) fn render_record_lit(
     fields: &[hir::Field],
     path: &str,
 ) -> RenderedLiteral {
+    if snippet_cap.is_none() {
+        return RenderedLiteral { literal: path.to_string(), detail: path.to_string() };
+    }
     let completions = fields.iter().enumerate().format_with(", ", |(idx, field), f| {
         if snippet_cap.is_some() {
             f(&format_args!("{}: ${{{}:()}}", field.name(db), idx + 1))
diff --git a/crates/ide-db/src/syntax_helpers/node_ext.rs b/crates/ide-db/src/syntax_helpers/node_ext.rs
index d7e7d59e15e..a34dc1b6950 100644
--- a/crates/ide-db/src/syntax_helpers/node_ext.rs
+++ b/crates/ide-db/src/syntax_helpers/node_ext.rs
@@ -173,7 +173,8 @@ pub fn walk_pat(pat: &ast::Pat, cb: &mut dyn FnMut(ast::Pat)) {
 }
 
 /// Preorder walk all the type's sub types.
-pub fn walk_ty(ty: &ast::Type, cb: &mut dyn FnMut(ast::Type)) {
+// FIXME: Make the control flow more proper
+pub fn walk_ty(ty: &ast::Type, cb: &mut dyn FnMut(ast::Type) -> bool) {
     let mut preorder = ty.syntax().preorder();
     while let Some(event) = preorder.next() {
         let node = match event {
@@ -184,10 +185,12 @@ pub fn walk_ty(ty: &ast::Type, cb: &mut dyn FnMut(ast::Type)) {
         match ast::Type::cast(node) {
             Some(ty @ ast::Type::MacroType(_)) => {
                 preorder.skip_subtree();
-                cb(ty)
+                cb(ty);
             }
             Some(ty) => {
-                cb(ty);
+                if cb(ty) {
+                    preorder.skip_subtree();
+                }
             }
             // skip const args
             None if ast::ConstArg::can_cast(kind) => {
diff --git a/crates/ide/src/inlay_hints.rs b/crates/ide/src/inlay_hints.rs
index 987230abfbd..86d25e2f5ad 100644
--- a/crates/ide/src/inlay_hints.rs
+++ b/crates/ide/src/inlay_hints.rs
@@ -24,7 +24,7 @@ mod chaining;
 mod param_name;
 mod binding_mode;
 mod bind_pat;
-mod discrimant;
+mod discriminant;
 
 #[derive(Clone, Debug, PartialEq, Eq)]
 pub struct InlayHintsConfig {
@@ -376,7 +376,7 @@ fn hints(
                 _ => None,
             },
             ast::Variant(v) => {
-                discrimant::hints(hints, famous_defs, config, file_id, &v)
+                discriminant::hints(hints, famous_defs, config, file_id, &v)
             },
             // FIXME: fn-ptr type, dyn fn type, and trait object type elisions
             ast::Type(_) => None,
diff --git a/crates/ide/src/inlay_hints/discrimant.rs b/crates/ide/src/inlay_hints/discriminant.rs
index f32c4bdf288..f32c4bdf288 100644
--- a/crates/ide/src/inlay_hints/discrimant.rs
+++ b/crates/ide/src/inlay_hints/discriminant.rs
diff --git a/crates/ide/src/inlay_hints/fn_lifetime_fn.rs b/crates/ide/src/inlay_hints/fn_lifetime_fn.rs
index 1f5bcea63a2..2aa5e3dc734 100644
--- a/crates/ide/src/inlay_hints/fn_lifetime_fn.rs
+++ b/crates/ide/src/inlay_hints/fn_lifetime_fn.rs
@@ -59,9 +59,14 @@ pub(super) fn hints(
                         r.amp_token(),
                         lifetime,
                         is_elided,
-                    ))
+                    ));
+                    false
                 }
-                _ => (),
+                ast::Type::FnPtrType(_) => true,
+                ast::Type::PathType(t) => {
+                    t.path().and_then(|it| it.segment()).and_then(|it| it.param_list()).is_some()
+                }
+                _ => false,
             })
         });
         acc
@@ -146,8 +151,13 @@ pub(super) fn hints(
                         is_trivial = false;
                         acc.push(mk_lt_hint(amp, output_lt.to_string()));
                     }
+                    false
+                }
+                ast::Type::FnPtrType(_) => true,
+                ast::Type::PathType(t) => {
+                    t.path().and_then(|it| it.segment()).and_then(|it| it.param_list()).is_some()
                 }
-                _ => (),
+                _ => false,
             })
         }
     }
@@ -298,4 +308,18 @@ impl () {
 "#,
         );
     }
+
+    #[test]
+    fn hints_lifetimes_skip_fn_likes() {
+        check_with_config(
+            InlayHintsConfig {
+                lifetime_elision_hints: LifetimeElisionHints::Always,
+                ..TEST_CONFIG
+            },
+            r#"
+fn fn_ptr(a: fn(&()) -> &()) {}
+fn fn_trait<>(a: impl Fn(&()) -> &()) {}
+"#,
+        );
+    }
 }
diff --git a/crates/rust-analyzer/src/diagnostics/to_proto.rs b/crates/rust-analyzer/src/diagnostics/to_proto.rs
index 473a0eb0eb8..acb416a0689 100644
--- a/crates/rust-analyzer/src/diagnostics/to_proto.rs
+++ b/crates/rust-analyzer/src/diagnostics/to_proto.rs
@@ -191,6 +191,7 @@ fn map_rust_child_diagnostic(
 
     let mut edit_map: HashMap<lsp_types::Url, Vec<lsp_types::TextEdit>> = HashMap::new();
     let mut suggested_replacements = Vec::new();
+    let mut is_preferred = true;
     for &span in &spans {
         if let Some(suggested_replacement) = &span.suggested_replacement {
             if !suggested_replacement.is_empty() {
@@ -209,6 +210,8 @@ fn map_rust_child_diagnostic(
             ) {
                 edit_map.entry(location.uri).or_default().push(edit);
             }
+            is_preferred &=
+                matches!(span.suggestion_applicability, Some(Applicability::MachineApplicable));
         }
     }
 
@@ -251,7 +254,7 @@ fn map_rust_child_diagnostic(
                         document_changes: None,
                         change_annotations: None,
                     }),
-                    is_preferred: Some(true),
+                    is_preferred: Some(is_preferred),
                     data: None,
                     command: None,
                 },
diff --git a/crates/rust-analyzer/tests/slow-tests/tidy.rs b/crates/rust-analyzer/tests/slow-tests/tidy.rs
index a5af948b08f..35b5af73192 100644
--- a/crates/rust-analyzer/tests/slow-tests/tidy.rs
+++ b/crates/rust-analyzer/tests/slow-tests/tidy.rs
@@ -193,6 +193,7 @@ MIT OR Apache-2.0
 MIT OR Apache-2.0 OR Zlib
 MIT OR Zlib OR Apache-2.0
 MIT/Apache-2.0
+Unlicense OR MIT
 Unlicense/MIT
 Zlib OR Apache-2.0 OR MIT
 "
diff --git a/crates/test-utils/src/minicore.rs b/crates/test-utils/src/minicore.rs
index 750b64fea6f..3ca63fcab90 100644
--- a/crates/test-utils/src/minicore.rs
+++ b/crates/test-utils/src/minicore.rs
@@ -382,6 +382,12 @@ pub mod ops {
         type Output;
         fn add(self, rhs: Rhs) -> Self::Output;
     }
+
+    #[lang = "add_assign"]
+    #[const_trait]
+    pub trait AddAssign<Rhs = Self> {
+        fn add_assign(&mut self, rhs: Rhs);
+    }
     // endregion:add
 
     // region:generator
diff --git a/editors/code/package.json b/editors/code/package.json
index b45058a6cf3..89ff64fca73 100644
--- a/editors/code/package.json
+++ b/editors/code/package.json
@@ -411,6 +411,11 @@
                     "default": false,
                     "type": "boolean"
                 },
+                "rust-analyzer.diagnostics.useRustcErrorCode": {
+                    "markdownDescription": "Whether to use the rustc error code.",
+                    "default": false,
+                    "type": "boolean"
+                },
                 "$generated-start": {},
                 "rust-analyzer.assist.emitMustUse": {
                     "markdownDescription": "Whether to insert #[must_use] when generating `as_` methods\nfor enum variants.",
diff --git a/editors/code/src/client.ts b/editors/code/src/client.ts
index 23e039722ee..e6595340aae 100644
--- a/editors/code/src/client.ts
+++ b/editors/code/src/client.ts
@@ -100,12 +100,31 @@ export async function createClient(
                     }
                 },
             },
+            async provideInlayHints(document, viewPort, token, next) {
+                const inlays = await next(document, viewPort, token);
+                if (!inlays) {
+                    return inlays;
+                }
+                // U+200C is a zero-width non-joiner to prevent the editor from forming a ligature
+                // between code and hints
+                for (const inlay of inlays) {
+                    if (typeof inlay.label === "string") {
+                        inlay.label = `\u{200c}${inlay.label}\u{200c}`;
+                    } else if (Array.isArray(inlay.label)) {
+                        for (const it of inlay.label) {
+                            it.value = `\u{200c}${it.value}\u{200c}`;
+                        }
+                    }
+                }
+                return inlays;
+            },
             async handleDiagnostics(
                 uri: vscode.Uri,
                 diagnostics: vscode.Diagnostic[],
                 next: lc.HandleDiagnosticsSignature
             ) {
                 const preview = config.previewRustcOutput;
+                const errorCode = config.useRustcErrorCode;
                 diagnostics.forEach((diag, idx) => {
                     // Abuse the fact that VSCode leaks the LSP diagnostics data field through the
                     // Diagnostic class, if they ever break this we are out of luck and have to go
@@ -119,11 +138,20 @@ export async function createClient(
                         ?.rendered;
                     if (rendered) {
                         if (preview) {
-                            const index = rendered.match(/^(note|help):/m)?.index || 0;
+                            const index =
+                                rendered.match(/^(note|help):/m)?.index || rendered.length;
                             diag.message = rendered
                                 .substring(0, index)
                                 .replace(/^ -->[^\n]+\n/m, "");
                         }
+                        let value;
+                        if (errorCode) {
+                            if (typeof diag.code === "string" || typeof diag.code === "number") {
+                                value = diag.code;
+                            } else {
+                                value = diag.code?.value;
+                            }
+                        }
                         diag.code = {
                             target: vscode.Uri.from({
                                 scheme: "rust-analyzer-diagnostics-view",
@@ -131,7 +159,7 @@ export async function createClient(
                                 fragment: uri.toString(),
                                 query: idx.toString(),
                             }),
-                            value: "Click for full compiler diagnostic",
+                            value: value ?? "Click for full compiler diagnostic",
                         };
                     }
                 });
diff --git a/editors/code/src/config.ts b/editors/code/src/config.ts
index d8dbd1df16d..eb4f965291f 100644
--- a/editors/code/src/config.ts
+++ b/editors/code/src/config.ts
@@ -241,6 +241,10 @@ export class Config {
     get previewRustcOutput() {
         return this.get<boolean>("diagnostics.previewRustcOutput");
     }
+
+    get useRustcErrorCode() {
+        return this.get<boolean>("diagnostics.useRustcErrorCode");
+    }
 }
 
 const VarRegex = new RegExp(/\$\{(.+?)\}/g);
diff --git a/xtask/Cargo.toml b/xtask/Cargo.toml
index 95e27beab5d..2dd01796c6e 100644
--- a/xtask/Cargo.toml
+++ b/xtask/Cargo.toml
@@ -12,4 +12,5 @@ flate2 = "1.0.24"
 write-json = "0.1.2"
 xshell = "0.2.2"
 xflags = "0.3.0"
+zip = { version = "0.6", default-features = false, features = ["deflate", "time"] }
 # Avoid adding more dependencies to this crate
diff --git a/xtask/src/dist.rs b/xtask/src/dist.rs
index 410276bc45b..74715c53eaa 100644
--- a/xtask/src/dist.rs
+++ b/xtask/src/dist.rs
@@ -1,12 +1,13 @@
 use std::{
     env,
     fs::File,
-    io,
+    io::{self, BufWriter},
     path::{Path, PathBuf},
 };
 
 use flate2::{write::GzEncoder, Compression};
 use xshell::{cmd, Shell};
+use zip::{write::FileOptions, DateTime, ZipWriter};
 
 use crate::{date_iso, flags, project_root};
 
@@ -89,6 +90,9 @@ fn dist_server(sh: &Shell, release: &str, target: &Target) -> anyhow::Result<()>
 
     let dst = Path::new("dist").join(&target.artifact_name);
     gzip(&target.server_path, &dst.with_extension("gz"))?;
+    if target_name.contains("-windows-") {
+        zip(&target.server_path, target.symbols_path.as_ref(), &dst.with_extension("zip"))?;
+    }
 
     Ok(())
 }
@@ -101,6 +105,38 @@ fn gzip(src_path: &Path, dest_path: &Path) -> anyhow::Result<()> {
     Ok(())
 }
 
+fn zip(src_path: &Path, symbols_path: Option<&PathBuf>, dest_path: &Path) -> anyhow::Result<()> {
+    let file = File::create(dest_path)?;
+    let mut writer = ZipWriter::new(BufWriter::new(file));
+    writer.start_file(
+        src_path.file_name().unwrap().to_str().unwrap(),
+        FileOptions::default()
+            .last_modified_time(
+                DateTime::from_time(std::fs::metadata(src_path)?.modified()?.into()).unwrap(),
+            )
+            .unix_permissions(0o755)
+            .compression_method(zip::CompressionMethod::Deflated)
+            .compression_level(Some(9)),
+    )?;
+    let mut input = io::BufReader::new(File::open(src_path)?);
+    io::copy(&mut input, &mut writer)?;
+    if let Some(symbols_path) = symbols_path {
+        writer.start_file(
+            symbols_path.file_name().unwrap().to_str().unwrap(),
+            FileOptions::default()
+                .last_modified_time(
+                    DateTime::from_time(std::fs::metadata(src_path)?.modified()?.into()).unwrap(),
+                )
+                .compression_method(zip::CompressionMethod::Deflated)
+                .compression_level(Some(9)),
+        )?;
+        let mut input = io::BufReader::new(File::open(symbols_path)?);
+        io::copy(&mut input, &mut writer)?;
+    }
+    writer.finish()?;
+    Ok(())
+}
+
 struct Target {
     name: String,
     server_path: PathBuf,