about summary refs log tree commit diff
diff options
context:
space:
mode:
-rw-r--r--Cargo.toml1
-rw-r--r--crates/hir-ty/src/display.rs225
-rw-r--r--crates/hir-ty/src/lib.rs5
-rw-r--r--crates/hir-ty/src/tests/display_source_code.rs2
-rw-r--r--crates/hir/src/lib.rs10
-rw-r--r--crates/ide-assists/src/handlers/promote_local_to_const.rs13
-rw-r--r--crates/ide-completion/src/completions/type.rs8
-rw-r--r--crates/ide-db/src/label.rs4
-rw-r--r--crates/ide-diagnostics/src/handlers/unresolved_field.rs17
-rw-r--r--crates/ide-diagnostics/src/handlers/unused_variables.rs21
-rw-r--r--crates/ide/src/hover/tests.rs66
11 files changed, 228 insertions, 144 deletions
diff --git a/Cargo.toml b/Cargo.toml
index 06b092a4490..f7e3ae51dfd 100644
--- a/Cargo.toml
+++ b/Cargo.toml
@@ -22,6 +22,7 @@ smol_str.opt-level = 3
 text-size.opt-level = 3
 # This speeds up `cargo xtask dist`.
 miniz_oxide.opt-level = 3
+salsa.opt-level = 3
 
 [profile.release]
 incremental = true
diff --git a/crates/hir-ty/src/display.rs b/crates/hir-ty/src/display.rs
index 42e33a9853b..cc2b2290e48 100644
--- a/crates/hir-ty/src/display.rs
+++ b/crates/hir-ty/src/display.rs
@@ -28,7 +28,7 @@ use intern::{Internable, Interned};
 use itertools::Itertools;
 use la_arena::ArenaMap;
 use smallvec::SmallVec;
-use stdx::never;
+use stdx::{never, IsNoneOr};
 use triomphe::Arc;
 
 use crate::{
@@ -41,9 +41,9 @@ use crate::{
     mir::pad16,
     primitive, to_assoc_type_id,
     utils::{self, detect_variant_from_bytes, generics, ClosureSubst},
-    AdtId, AliasEq, AliasTy, Binders, CallableDefId, CallableSig, Const, ConstScalar, ConstValue,
-    DomainGoal, FnAbi, GenericArg, GenericArgData, ImplTraitId, Interner, Lifetime, LifetimeData,
-    LifetimeOutlives, MemoryMap, Mutability, OpaqueTy, ProjectionTy, ProjectionTyExt,
+    AdtId, AliasEq, AliasTy, Binders, CallableDefId, CallableSig, ConcreteConst, Const,
+    ConstScalar, ConstValue, DomainGoal, FnAbi, GenericArg, ImplTraitId, Interner, Lifetime,
+    LifetimeData, LifetimeOutlives, MemoryMap, Mutability, OpaqueTy, ProjectionTy, ProjectionTyExt,
     QuantifiedWhereClause, Scalar, Substitution, TraitEnvironment, TraitRef, TraitRefExt, Ty,
     TyExt, WhereClause,
 };
@@ -60,11 +60,18 @@ impl HirWrite for String {}
 impl HirWrite for fmt::Formatter<'_> {}
 
 pub struct HirFormatter<'a> {
+    /// The database handle
     pub db: &'a dyn HirDatabase,
+    /// The sink to write into
     fmt: &'a mut dyn HirWrite,
+    /// A buffer to intercept writes with, this allows us to track the overall size of the formatted output.
     buf: String,
+    /// The current size of the formatted output.
     curr_size: usize,
-    pub(crate) max_size: Option<usize>,
+    /// Size from which we should truncate the output.
+    max_size: Option<usize>,
+    /// When rendering something that has a concept of "children" (like fields in a struct), this limits
+    /// how many should be rendered.
     pub entity_limit: Option<usize>,
     omit_verbose_types: bool,
     closure_style: ClosureStyle,
@@ -304,7 +311,6 @@ impl DisplayTarget {
 #[derive(Debug)]
 pub enum DisplaySourceCodeError {
     PathNotFound,
-    UnknownType,
     Coroutine,
     OpaqueType,
 }
@@ -418,6 +424,7 @@ impl HirDisplay for ProjectionTy {
         let proj_params = &self.substitution.as_slice(Interner)[..proj_params_count];
         if !proj_params.is_empty() {
             write!(f, "<")?;
+            // FIXME use `hir_fmt_generics` here
             f.write_joined(proj_params, ", ")?;
             write!(f, ">")?;
         }
@@ -967,6 +974,7 @@ impl HirDisplay for Ty {
                             .chain(fn_params)
                             .flatten();
                         write!(f, "<")?;
+                        // FIXME use `hir_fmt_generics` here
                         f.write_joined(params, ", ")?;
                         write!(f, ">")?;
                     }
@@ -1037,6 +1045,7 @@ impl HirDisplay for Ty {
                     // FIXME: reconsider the generic args order upon formatting?
                     if parameters.len(Interner) > 0 {
                         write!(f, "<")?;
+                        // FIXME use `hir_fmt_generics` here
                         f.write_joined(parameters.as_slice(Interner), ", ")?;
                         write!(f, ">")?;
                     }
@@ -1287,11 +1296,10 @@ impl HirDisplay for Ty {
             }
             TyKind::Error => {
                 if f.display_target.is_source_code() {
-                    return Err(HirDisplayError::DisplaySourceCodeError(
-                        DisplaySourceCodeError::UnknownType,
-                    ));
+                    f.write_char('_')?;
+                } else {
+                    write!(f, "{{unknown}}")?;
                 }
-                write!(f, "{{unknown}}")?;
             }
             TyKind::InferenceVar(..) => write!(f, "_")?,
             TyKind::Coroutine(_, subst) => {
@@ -1331,98 +1339,90 @@ fn hir_fmt_generics(
     parameters: &Substitution,
     generic_def: Option<hir_def::GenericDefId>,
 ) -> Result<(), HirDisplayError> {
-    let db = f.db;
-    if parameters.len(Interner) > 0 {
-        use std::cmp::Ordering;
-        let param_compare =
-            |a: &GenericArg, b: &GenericArg| match (a.data(Interner), b.data(Interner)) {
-                (crate::GenericArgData::Lifetime(_), crate::GenericArgData::Lifetime(_)) => {
-                    Ordering::Equal
-                }
-                (crate::GenericArgData::Lifetime(_), _) => Ordering::Less,
-                (_, crate::GenericArgData::Lifetime(_)) => Ordering::Less,
-                (_, _) => Ordering::Equal,
-            };
-        let parameters_to_write = if f.display_target.is_source_code() || f.omit_verbose_types() {
-            match generic_def
-                .map(|generic_def_id| db.generic_defaults(generic_def_id))
-                .filter(|defaults| !defaults.is_empty())
-            {
-                None => parameters.as_slice(Interner),
-                Some(default_parameters) => {
-                    fn should_show(
-                        parameter: &GenericArg,
-                        default_parameters: &[Binders<GenericArg>],
-                        i: usize,
-                        parameters: &Substitution,
-                    ) -> bool {
-                        if parameter.ty(Interner).map(|it| it.kind(Interner))
-                            == Some(&TyKind::Error)
-                        {
-                            return true;
-                        }
-                        if let Some(ConstValue::Concrete(c)) =
-                            parameter.constant(Interner).map(|it| &it.data(Interner).value)
-                        {
-                            if c.interned == ConstScalar::Unknown {
-                                return true;
-                            }
-                        }
-                        if let Some(crate::LifetimeData::Static | crate::LifetimeData::Error) =
-                            parameter.lifetime(Interner).map(|it| it.data(Interner))
-                        {
-                            return true;
+    if parameters.is_empty(Interner) {
+        return Ok(());
+    }
+
+    let parameters_to_write =
+        generic_args_sans_defaults(f, generic_def, parameters.as_slice(Interner));
+    if !parameters_to_write.is_empty() {
+        write!(f, "<")?;
+        hir_fmt_generic_arguments(f, parameters_to_write)?;
+        write!(f, ">")?;
+    }
+
+    Ok(())
+}
+
+fn generic_args_sans_defaults<'ga>(
+    f: &mut HirFormatter<'_>,
+    generic_def: Option<hir_def::GenericDefId>,
+    parameters: &'ga [GenericArg],
+) -> &'ga [GenericArg] {
+    if f.display_target.is_source_code() || f.omit_verbose_types() {
+        match generic_def
+            .map(|generic_def_id| f.db.generic_defaults(generic_def_id))
+            .filter(|it| !it.is_empty())
+        {
+            None => parameters,
+            Some(default_parameters) => {
+                let should_show = |arg: &GenericArg, i: usize| {
+                    let is_err = |arg: &GenericArg| match arg.data(Interner) {
+                        chalk_ir::GenericArgData::Lifetime(it) => {
+                            *it.data(Interner) == LifetimeData::Error
                         }
-                        let default_parameter = match default_parameters.get(i) {
-                            Some(it) => it,
-                            None => return true,
-                        };
-                        let actual_default =
-                            default_parameter.clone().substitute(Interner, &parameters);
-                        parameter != &actual_default
+                        chalk_ir::GenericArgData::Ty(it) => *it.kind(Interner) == TyKind::Error,
+                        chalk_ir::GenericArgData::Const(it) => matches!(
+                            it.data(Interner).value,
+                            ConstValue::Concrete(ConcreteConst {
+                                interned: ConstScalar::Unknown,
+                                ..
+                            })
+                        ),
+                    };
+                    // if the arg is error like, render it to inform the user
+                    if is_err(arg) {
+                        return true;
                     }
-                    let mut default_from = 0;
-                    for (i, parameter) in parameters.iter(Interner).enumerate() {
-                        if should_show(parameter, &default_parameters, i, parameters) {
-                            default_from = i + 1;
-                        }
+                    // otherwise, if the arg is equal to the param default, hide it (unless the
+                    // default is an error which can happen for the trait Self type)
+                    default_parameters.get(i).is_none_or(|default_parameter| {
+                        // !is_err(default_parameter.skip_binders())
+                        //     &&
+                        arg != &default_parameter.clone().substitute(Interner, &parameters)
+                    })
+                };
+                let mut default_from = 0;
+                for (i, parameter) in parameters.iter().enumerate() {
+                    if should_show(parameter, i) {
+                        default_from = i + 1;
                     }
-                    &parameters.as_slice(Interner)[0..default_from]
-                }
-            }
-        } else {
-            parameters.as_slice(Interner)
-        };
-        //FIXME: Should handle the ordering of lifetimes when creating substitutions
-        let mut parameters_to_write = parameters_to_write.to_vec();
-        parameters_to_write.sort_by(param_compare);
-        if !parameters_to_write.is_empty() {
-            write!(f, "<")?;
-            let mut first = true;
-            for generic_arg in parameters_to_write {
-                if !first {
-                    write!(f, ", ")?;
-                }
-                first = false;
-                if f.display_target.is_source_code() {
-                    match generic_arg.data(Interner) {
-                        GenericArgData::Lifetime(l)
-                            if matches!(l.data(Interner), LifetimeData::Error) =>
-                        {
-                            write!(f, "'_")
-                        }
-                        GenericArgData::Ty(t) if matches!(t.kind(Interner), TyKind::Error) => {
-                            write!(f, "_")
-                        }
-                        _ => generic_arg.hir_fmt(f),
-                    }?
-                } else {
-                    generic_arg.hir_fmt(f)?;
                 }
+                &parameters[0..default_from]
             }
+        }
+    } else {
+        parameters
+    }
+}
 
-            write!(f, ">")?;
+fn hir_fmt_generic_arguments(
+    f: &mut HirFormatter<'_>,
+    parameters: &[GenericArg],
+) -> Result<(), HirDisplayError> {
+    let mut first = true;
+    let lifetime_offset = parameters.iter().position(|arg| arg.lifetime(Interner).is_some());
+
+    let (ty_or_const, lifetimes) = match lifetime_offset {
+        Some(offset) => parameters.split_at(offset),
+        None => (parameters, &[][..]),
+    };
+    for generic_arg in lifetimes.iter().chain(ty_or_const) {
+        if !first {
+            write!(f, ", ")?;
         }
+        first = false;
+        generic_arg.hir_fmt(f)?;
     }
     Ok(())
 }
@@ -1544,20 +1544,29 @@ fn write_bounds_like_dyn_trait(
                 f.start_location_link(trait_.into());
                 write!(f, "{}", f.db.trait_data(trait_).name.display(f.db.upcast()))?;
                 f.end_location_link();
-                if let [_, params @ ..] = trait_ref.substitution.as_slice(Interner) {
-                    if is_fn_trait {
+                if is_fn_trait {
+                    if let [_self, params @ ..] = trait_ref.substitution.as_slice(Interner) {
                         if let Some(args) =
                             params.first().and_then(|it| it.assert_ty_ref(Interner).as_tuple())
                         {
                             write!(f, "(")?;
-                            f.write_joined(args.as_slice(Interner), ", ")?;
+                            hir_fmt_generic_arguments(f, args.as_slice(Interner))?;
                             write!(f, ")")?;
                         }
-                    } else if !params.is_empty() {
-                        write!(f, "<")?;
-                        f.write_joined(params, ", ")?;
-                        // there might be assoc type bindings, so we leave the angle brackets open
-                        angle_open = true;
+                    }
+                } else {
+                    let params = generic_args_sans_defaults(
+                        f,
+                        Some(trait_.into()),
+                        trait_ref.substitution.as_slice(Interner),
+                    );
+                    if let [_self, params @ ..] = params {
+                        if !params.is_empty() {
+                            write!(f, "<")?;
+                            hir_fmt_generic_arguments(f, params)?;
+                            // there might be assoc type bindings, so we leave the angle brackets open
+                            angle_open = true;
+                        }
                     }
                 }
             }
@@ -1609,9 +1618,9 @@ fn write_bounds_like_dyn_trait(
                     let proj_arg_count = generics(f.db.upcast(), assoc_ty_id.into()).len_self();
                     if proj_arg_count > 0 {
                         write!(f, "<")?;
-                        f.write_joined(
+                        hir_fmt_generic_arguments(
+                            f,
                             &proj.substitution.as_slice(Interner)[..proj_arg_count],
-                            ", ",
                         )?;
                         write!(f, ">")?;
                     }
@@ -1670,6 +1679,7 @@ fn fmt_trait_ref(
     f.end_location_link();
     if tr.substitution.len(Interner) > 1 {
         write!(f, "<")?;
+        // FIXME use `hir_fmt_generics` here
         f.write_joined(&tr.substitution.as_slice(Interner)[1..], ", ")?;
         write!(f, ">")?;
     }
@@ -1728,8 +1738,6 @@ impl HirDisplay for Lifetime {
 impl HirDisplay for LifetimeData {
     fn hir_fmt(&self, f: &mut HirFormatter<'_>) -> Result<(), HirDisplayError> {
         match self {
-            LifetimeData::BoundVar(idx) => idx.hir_fmt(f),
-            LifetimeData::InferenceVar(_) => write!(f, "_"),
             LifetimeData::Placeholder(idx) => {
                 let id = lt_from_placeholder_idx(f.db, *idx);
                 let generics = generics(f.db.upcast(), id.parent);
@@ -1737,6 +1745,9 @@ impl HirDisplay for LifetimeData {
                 write!(f, "{}", param_data.name.display(f.db.upcast()))?;
                 Ok(())
             }
+            _ if f.display_target.is_source_code() => write!(f, "'_"),
+            LifetimeData::BoundVar(idx) => idx.hir_fmt(f),
+            LifetimeData::InferenceVar(_) => write!(f, "_"),
             LifetimeData::Static => write!(f, "'static"),
             LifetimeData::Error => write!(f, "'{{error}}"),
             LifetimeData::Erased => Ok(()),
diff --git a/crates/hir-ty/src/lib.rs b/crates/hir-ty/src/lib.rs
index b9f4c57366b..1727cec9893 100644
--- a/crates/hir-ty/src/lib.rs
+++ b/crates/hir-ty/src/lib.rs
@@ -56,7 +56,6 @@ use base_db::salsa::impl_intern_value_trivial;
 use chalk_ir::{
     fold::{Shift, TypeFoldable},
     interner::HasInterner,
-    visit::{TypeSuperVisitable, TypeVisitable, TypeVisitor},
     NoSolution,
 };
 use either::Either;
@@ -98,7 +97,9 @@ pub use traits::TraitEnvironment;
 pub use utils::{all_super_traits, is_fn_unsafe_to_call};
 
 pub use chalk_ir::{
-    cast::Cast, AdtId, BoundVar, DebruijnIndex, Mutability, Safety, Scalar, TyVariableKind,
+    cast::Cast,
+    visit::{TypeSuperVisitable, TypeVisitable, TypeVisitor},
+    AdtId, BoundVar, DebruijnIndex, Mutability, Safety, Scalar, TyVariableKind,
 };
 
 pub type ForeignDefId = chalk_ir::ForeignDefId<Interner>;
diff --git a/crates/hir-ty/src/tests/display_source_code.rs b/crates/hir-ty/src/tests/display_source_code.rs
index e4c5d709da7..e8369caa771 100644
--- a/crates/hir-ty/src/tests/display_source_code.rs
+++ b/crates/hir-ty/src/tests/display_source_code.rs
@@ -85,7 +85,7 @@ fn render_dyn_for_ty() {
 trait Foo<'a> {}
 
 fn foo(foo: &dyn for<'a> Foo<'a>) {}
-    // ^^^ &dyn Foo<'{error}>
+    // ^^^ &dyn Foo<'_>
 "#,
     );
 }
diff --git a/crates/hir/src/lib.rs b/crates/hir/src/lib.rs
index 8556d35a43b..6bffb0c5e7b 100644
--- a/crates/hir/src/lib.rs
+++ b/crates/hir/src/lib.rs
@@ -4711,10 +4711,12 @@ impl Type {
                 if let WhereClause::Implemented(trait_ref) = pred.skip_binders() {
                     cb(type_.clone());
                     // skip the self type. it's likely the type we just got the bounds from
-                    for ty in
-                        trait_ref.substitution.iter(Interner).skip(1).filter_map(|a| a.ty(Interner))
-                    {
-                        walk_type(db, &type_.derived(ty.clone()), cb);
+                    if let [self_ty, params @ ..] = trait_ref.substitution.as_slice(Interner) {
+                        for ty in
+                            params.iter().filter(|&ty| ty != self_ty).filter_map(|a| a.ty(Interner))
+                        {
+                            walk_type(db, &type_.derived(ty.clone()), cb);
+                        }
                     }
                 }
             }
diff --git a/crates/ide-assists/src/handlers/promote_local_to_const.rs b/crates/ide-assists/src/handlers/promote_local_to_const.rs
index 67fea772c79..7c2dc0e0c10 100644
--- a/crates/ide-assists/src/handlers/promote_local_to_const.rs
+++ b/crates/ide-assists/src/handlers/promote_local_to_const.rs
@@ -59,10 +59,7 @@ pub(crate) fn promote_local_to_const(acc: &mut Assists, ctx: &AssistContext<'_>)
 
     let ty = match ty.display_source_code(ctx.db(), module.into(), false) {
         Ok(ty) => ty,
-        Err(_) => {
-            cov_mark::hit!(promote_local_not_applicable_if_ty_not_inferred);
-            return None;
-        }
+        Err(_) => return None,
     };
 
     let initializer = let_stmt.initializer()?;
@@ -315,14 +312,18 @@ fn foo() {
 
     #[test]
     fn not_applicable_unknown_ty() {
-        cov_mark::check!(promote_local_not_applicable_if_ty_not_inferred);
-        check_assist_not_applicable(
+        check_assist(
             promote_local_to_const,
             r"
 fn foo() {
     let x$0 = bar();
 }
 ",
+            r"
+fn foo() {
+    const $0X: _ = bar();
+}
+",
         );
     }
 
diff --git a/crates/ide-completion/src/completions/type.rs b/crates/ide-completion/src/completions/type.rs
index e4678089462..2361d14aae7 100644
--- a/crates/ide-completion/src/completions/type.rs
+++ b/crates/ide-completion/src/completions/type.rs
@@ -226,7 +226,7 @@ pub(crate) fn complete_ascribed_type(
     if !path_ctx.is_trivial_path() {
         return None;
     }
-    let x = match ascription {
+    let ty = match ascription {
         TypeAscriptionTarget::Let(pat) | TypeAscriptionTarget::FnParam(pat) => {
             ctx.sema.type_of_pat(pat.as_ref()?)
         }
@@ -235,7 +235,9 @@ pub(crate) fn complete_ascribed_type(
         }
     }?
     .adjusted();
-    let ty_string = x.display_source_code(ctx.db, ctx.module.into(), true).ok()?;
-    acc.add(render_type_inference(ty_string, ctx));
+    if !ty.is_unknown() {
+        let ty_string = ty.display_source_code(ctx.db, ctx.module.into(), true).ok()?;
+        acc.add(render_type_inference(ty_string, ctx));
+    }
     None
 }
diff --git a/crates/ide-db/src/label.rs b/crates/ide-db/src/label.rs
index 4b6d54b5eab..919c1273e5a 100644
--- a/crates/ide-db/src/label.rs
+++ b/crates/ide-db/src/label.rs
@@ -1,6 +1,8 @@
 //! See [`Label`]
 use std::fmt;
 
+use stdx::always;
+
 /// A type to specify UI label, like an entry in the list of assists. Enforces
 /// proper casing:
 ///
@@ -30,7 +32,7 @@ impl From<Label> for String {
 
 impl Label {
     pub fn new(label: String) -> Label {
-        assert!(label.starts_with(char::is_uppercase) && !label.ends_with('.'));
+        always!(label.starts_with(char::is_uppercase) && !label.ends_with('.'));
         Label(label)
     }
 }
diff --git a/crates/ide-diagnostics/src/handlers/unresolved_field.rs b/crates/ide-diagnostics/src/handlers/unresolved_field.rs
index 7a03f176ac2..41357b59622 100644
--- a/crates/ide-diagnostics/src/handlers/unresolved_field.rs
+++ b/crates/ide-diagnostics/src/handlers/unresolved_field.rs
@@ -81,14 +81,15 @@ fn field_fix(ctx: &DiagnosticsContext<'_>, d: &hir::UnresolvedField) -> Option<A
     let adt = d.receiver.strip_references().as_adt()?;
     let target_module = adt.module(ctx.sema.db);
 
-    let suggested_type =
-        if let Some(new_field_type) = ctx.sema.type_of_expr(&expr).map(|v| v.adjusted()) {
-            let display =
-                new_field_type.display_source_code(ctx.sema.db, target_module.into(), false).ok();
-            make::ty(display.as_deref().unwrap_or("()"))
-        } else {
-            make::ty("()")
-        };
+    let suggested_type = if let Some(new_field_type) =
+        ctx.sema.type_of_expr(&expr).map(|v| v.adjusted()).filter(|it| !it.is_unknown())
+    {
+        let display =
+            new_field_type.display_source_code(ctx.sema.db, target_module.into(), false).ok();
+        make::ty(display.as_deref().unwrap_or("()"))
+    } else {
+        make::ty("()")
+    };
 
     if !is_editable_crate(target_module.krate(), ctx.sema.db) {
         return None;
diff --git a/crates/ide-diagnostics/src/handlers/unused_variables.rs b/crates/ide-diagnostics/src/handlers/unused_variables.rs
index cd251faab9a..f69209a10a9 100644
--- a/crates/ide-diagnostics/src/handlers/unused_variables.rs
+++ b/crates/ide-diagnostics/src/handlers/unused_variables.rs
@@ -1,8 +1,10 @@
+use hir::Name;
 use ide_db::{
     assists::{Assist, AssistId, AssistKind},
     base_db::FileRange,
     label::Label,
     source_change::SourceChange,
+    RootDatabase,
 };
 use text_edit::TextEdit;
 
@@ -21,7 +23,7 @@ pub(crate) fn unused_variables(
         return None;
     }
     let diagnostic_range = ctx.sema.diagnostics_display_range(ast);
-    let var_name = d.local.primary_source(ctx.sema.db).syntax().to_string();
+    let var_name = d.local.name(ctx.sema.db);
     Some(
         Diagnostic::new_with_syntax_node_ptr(
             ctx,
@@ -29,23 +31,32 @@ pub(crate) fn unused_variables(
             "unused variable",
             ast,
         )
-        .with_fixes(fixes(&var_name, diagnostic_range, ast.file_id.is_macro()))
+        .with_fixes(fixes(ctx.sema.db, var_name, diagnostic_range, ast.file_id.is_macro()))
         .experimental(),
     )
 }
 
-fn fixes(var_name: &String, diagnostic_range: FileRange, is_in_marco: bool) -> Option<Vec<Assist>> {
+fn fixes(
+    db: &RootDatabase,
+    var_name: Name,
+    diagnostic_range: FileRange,
+    is_in_marco: bool,
+) -> Option<Vec<Assist>> {
     if is_in_marco {
         return None;
     }
     Some(vec![Assist {
         id: AssistId("unscore_unused_variable_name", AssistKind::QuickFix),
-        label: Label::new(format!("Rename unused {} to _{}", var_name, var_name)),
+        label: Label::new(format!(
+            "Rename unused {} to _{}",
+            var_name.display(db),
+            var_name.display(db)
+        )),
         group: None,
         target: diagnostic_range.range,
         source_change: Some(SourceChange::from_text_edit(
             diagnostic_range.file_id,
-            TextEdit::replace(diagnostic_range.range, format!("_{}", var_name)),
+            TextEdit::replace(diagnostic_range.range, format!("_{}", var_name.display(db))),
         )),
         trigger_signature_help: false,
     }])
diff --git a/crates/ide/src/hover/tests.rs b/crates/ide/src/hover/tests.rs
index 754fb2eccdb..67f10f0374d 100644
--- a/crates/ide/src/hover/tests.rs
+++ b/crates/ide/src/hover/tests.rs
@@ -3301,12 +3301,12 @@ fn foo(ar$0g: &impl Foo<S>) {}
 fn test_hover_dyn_return_has_goto_type_action() {
     check_actions(
         r#"
-trait Foo {}
+trait Foo<T> {}
 struct S;
-impl Foo for S {}
+impl Foo<S> for S {}
 
 struct B<T>{}
-fn foo() -> B<dyn Foo> {}
+fn foo() -> B<dyn Foo<S>> {}
 
 fn main() { let s$0t = foo(); }
 "#,
@@ -3320,8 +3320,8 @@ fn main() { let s$0t = foo(); }
                                 file_id: FileId(
                                     0,
                                 ),
-                                full_range: 42..55,
-                                focus_range: 49..50,
+                                full_range: 48..61,
+                                focus_range: 55..56,
                                 name: "B",
                                 kind: Struct,
                                 description: "struct B<T>",
@@ -3333,11 +3333,24 @@ fn main() { let s$0t = foo(); }
                                 file_id: FileId(
                                     0,
                                 ),
-                                full_range: 0..12,
+                                full_range: 0..15,
                                 focus_range: 6..9,
                                 name: "Foo",
                                 kind: Trait,
-                                description: "trait Foo",
+                                description: "trait Foo<T>",
+                            },
+                        },
+                        HoverGotoTypeData {
+                            mod_path: "test::S",
+                            nav: NavigationTarget {
+                                file_id: FileId(
+                                    0,
+                                ),
+                                full_range: 16..25,
+                                focus_range: 23..24,
+                                name: "S",
+                                kind: Struct,
+                                description: "struct S",
                             },
                         },
                     ],
@@ -4260,6 +4273,10 @@ fn foo<T$0: ?Sized + Sized + Sized>() {}
                 ```
             "#]],
         );
+    }
+
+    #[test]
+    fn mixed2() {
         check(
             r#"
 //- minicore: sized
@@ -7924,3 +7941,38 @@ struct Pedro$0<'a> {
         "#]],
     )
 }
+
+#[test]
+fn hover_impl_trait_arg_self() {
+    check(
+        r#"
+trait T<Rhs = Self> {}
+fn main(a$0: impl T) {}
+"#,
+        expect![[r#"
+            *a*
+
+            ```rust
+            a: impl T + ?Sized
+            ```
+        "#]],
+    );
+}
+
+#[test]
+fn hover_struct_default_arg_self() {
+    check(
+        r#"
+struct T<Rhs = Self> {}
+fn main(a$0: T) {}
+"#,
+        expect![[r#"
+            *a*
+
+            ```rust
+            // size = 0, align = 1
+            a: T
+            ```
+        "#]],
+    );
+}