about summary refs log tree commit diff
diff options
context:
space:
mode:
authorLukas Wirth <lukastw97@gmail.com>2024-01-16 16:15:56 +0100
committerLukas Wirth <lukastw97@gmail.com>2024-01-16 19:17:33 +0100
commit82e835549223d0f65935e3f96fdb996cc2f0a862 (patch)
tree21dce2b0efc3d6ff964be788062b6fab0e92f75c
parent384488c15702e1207b704113f3f4592e3813c29c (diff)
downloadrust-82e835549223d0f65935e3f96fdb996cc2f0a862.tar.gz
rust-82e835549223d0f65935e3f96fdb996cc2f0a862.zip
feat: Show notable trait impls on hover
-rw-r--r--crates/hir-def/src/db.rs7
-rw-r--r--crates/hir-def/src/lang_item.rs40
-rw-r--r--crates/hir/src/lib.rs24
-rw-r--r--crates/ide-db/src/defs.rs24
-rw-r--r--crates/ide/src/hover/render.rs79
-rw-r--r--crates/ide/src/hover/tests.rs756
-rw-r--r--crates/test-utils/src/minicore.rs2
7 files changed, 557 insertions, 375 deletions
diff --git a/crates/hir-def/src/db.rs b/crates/hir-def/src/db.rs
index 2fd2f18ee45..d25d41c2cfa 100644
--- a/crates/hir-def/src/db.rs
+++ b/crates/hir-def/src/db.rs
@@ -200,7 +200,7 @@ pub trait DefDatabase: InternDatabase + ExpandDatabase + Upcast<dyn ExpandDataba
     fn attrs(&self, def: AttrDefId) -> Attrs;
 
     #[salsa::transparent]
-    #[salsa::invoke(lang_item::lang_attr_query)]
+    #[salsa::invoke(lang_item::lang_attr)]
     fn lang_attr(&self, def: AttrDefId) -> Option<LangItem>;
 
     // endregion:attrs
@@ -228,6 +228,11 @@ pub trait DefDatabase: InternDatabase + ExpandDatabase + Upcast<dyn ExpandDataba
     #[salsa::invoke(LangItems::crate_lang_items_query)]
     fn crate_lang_items(&self, krate: CrateId) -> Option<Arc<LangItems>>;
 
+    #[salsa::invoke(crate::lang_item::notable_traits_in_deps)]
+    fn notable_traits_in_deps(&self, krate: CrateId) -> Arc<[Arc<[TraitId]>]>;
+    #[salsa::invoke(crate::lang_item::crate_notable_traits)]
+    fn crate_notable_traits(&self, krate: CrateId) -> Option<Arc<[TraitId]>>;
+
     fn crate_supports_no_std(&self, crate_id: CrateId) -> bool;
 }
 
diff --git a/crates/hir-def/src/lang_item.rs b/crates/hir-def/src/lang_item.rs
index 45ba939b8cf..adb2d785439 100644
--- a/crates/hir-def/src/lang_item.rs
+++ b/crates/hir-def/src/lang_item.rs
@@ -184,17 +184,53 @@ impl LangItems {
         T: Into<AttrDefId> + Copy,
     {
         let _p = profile::span("collect_lang_item");
-        if let Some(lang_item) = db.lang_attr(item.into()) {
+        if let Some(lang_item) = lang_attr(db, item.into()) {
             self.items.entry(lang_item).or_insert_with(|| constructor(item));
         }
     }
 }
 
-pub(crate) fn lang_attr_query(db: &dyn DefDatabase, item: AttrDefId) -> Option<LangItem> {
+pub(crate) fn lang_attr(db: &dyn DefDatabase, item: AttrDefId) -> Option<LangItem> {
     let attrs = db.attrs(item);
     attrs.by_key("lang").string_value().and_then(|it| LangItem::from_str(&it))
 }
 
+pub(crate) fn notable_traits_in_deps(
+    db: &dyn DefDatabase,
+    krate: CrateId,
+) -> Arc<[Arc<[TraitId]>]> {
+    let _p = profile::span("notable_traits_in_deps").detail(|| format!("{krate:?}"));
+    let crate_graph = db.crate_graph();
+
+    Arc::from_iter(
+        crate_graph.transitive_deps(krate).filter_map(|krate| db.crate_notable_traits(krate)),
+    )
+}
+
+pub(crate) fn crate_notable_traits(db: &dyn DefDatabase, krate: CrateId) -> Option<Arc<[TraitId]>> {
+    let _p = profile::span("crate_notable_traits").detail(|| format!("{krate:?}"));
+
+    let mut traits = Vec::new();
+
+    let crate_def_map = db.crate_def_map(krate);
+
+    for (_, module_data) in crate_def_map.modules() {
+        for def in module_data.scope.declarations() {
+            if let ModuleDefId::TraitId(trait_) = def {
+                if db.attrs(trait_.into()).has_doc_notable_trait() {
+                    traits.push(trait_);
+                }
+            }
+        }
+    }
+
+    if traits.is_empty() {
+        None
+    } else {
+        Some(traits.into_iter().collect())
+    }
+}
+
 pub enum GenericRequirement {
     None,
     Minimum(usize),
diff --git a/crates/hir/src/lib.rs b/crates/hir/src/lib.rs
index ab42cbeecc5..9866393e540 100644
--- a/crates/hir/src/lib.rs
+++ b/crates/hir/src/lib.rs
@@ -2844,6 +2844,7 @@ impl AssocItem {
             AssocItem::TypeAlias(it) => Some(it.name(db)),
         }
     }
+
     pub fn module(self, db: &dyn HirDatabase) -> Module {
         match self {
             AssocItem::Function(f) => f.module(db),
@@ -2851,6 +2852,7 @@ impl AssocItem {
             AssocItem::TypeAlias(t) => t.module(db),
         }
     }
+
     pub fn container(self, db: &dyn HirDatabase) -> AssocItemContainer {
         let container = match self {
             AssocItem::Function(it) => it.id.lookup(db.upcast()).container,
@@ -2886,6 +2888,27 @@ impl AssocItem {
             AssocItemContainer::Impl(i) => i.trait_(db),
         }
     }
+
+    pub fn as_function(self) -> Option<Function> {
+        match self {
+            Self::Function(v) => Some(v),
+            _ => None,
+        }
+    }
+
+    pub fn as_const(self) -> Option<Const> {
+        match self {
+            Self::Const(v) => Some(v),
+            _ => None,
+        }
+    }
+
+    pub fn as_type_alias(self) -> Option<TypeAlias> {
+        match self {
+            Self::TypeAlias(v) => Some(v),
+            _ => None,
+        }
+    }
 }
 
 impl HasVisibility for AssocItem {
@@ -3024,6 +3047,7 @@ impl LocalSource {
 
 impl Local {
     pub fn is_param(self, db: &dyn HirDatabase) -> bool {
+        // FIXME: This parses!
         let src = self.primary_source(db);
         match src.source.value {
             Either::Left(pat) => pat
diff --git a/crates/ide-db/src/defs.rs b/crates/ide-db/src/defs.rs
index 8f55f30a2dd..5995b318e8e 100644
--- a/crates/ide-db/src/defs.rs
+++ b/crates/ide-db/src/defs.rs
@@ -230,23 +230,15 @@ impl Definition {
             Definition::BuiltinType(it) => it.name().display(db).to_string(),
             Definition::Local(it) => {
                 let ty = it.ty(db);
-                let ty = ty.display_truncated(db, None);
+                let ty_display = ty.display_truncated(db, None);
                 let is_mut = if it.is_mut(db) { "mut " } else { "" };
-                let desc = match it.primary_source(db).into_ident_pat() {
-                    Some(ident) => {
-                        let name = it.name(db);
-                        let let_kw = if ident.syntax().parent().map_or(false, |p| {
-                            p.kind() == SyntaxKind::LET_STMT || p.kind() == SyntaxKind::LET_EXPR
-                        }) {
-                            "let "
-                        } else {
-                            ""
-                        };
-                        format!("{let_kw}{is_mut}{}: {ty}", name.display(db))
-                    }
-                    None => format!("{is_mut}self: {ty}"),
-                };
-                desc
+                if it.is_self(db) {
+                    format!("{is_mut}self: {ty_display}")
+                } else {
+                    let name = it.name(db);
+                    let let_kw = if it.is_param(db) { "" } else { "let " };
+                    format!("{let_kw}{is_mut}{}: {ty_display}", name.display(db))
+                }
             }
             Definition::SelfType(impl_def) => {
                 impl_def.self_ty(db).as_adt().and_then(|adt| Definition::Adt(adt).label(db))?
diff --git a/crates/ide/src/hover/render.rs b/crates/ide/src/hover/render.rs
index 9d449bd18ee..d3a9975b2b2 100644
--- a/crates/ide/src/hover/render.rs
+++ b/crates/ide/src/hover/render.rs
@@ -1,7 +1,10 @@
 //! Logic for rendering the different hover messages
+use std::{mem, ops::Not};
+
 use either::Either;
 use hir::{
-    Adt, AsAssocItem, CaptureKind, HasSource, HirDisplay, Layout, LayoutError, Semantics, TypeInfo,
+    db::DefDatabase, Adt, AsAssocItem, AssocItem, CaptureKind, HasCrate, HasSource, HirDisplay,
+    Layout, LayoutError, Semantics, TypeInfo,
 };
 use ide_db::{
     base_db::SourceDatabase,
@@ -390,7 +393,6 @@ pub(super) fn definition(
     let mod_path = definition_mod_path(db, &def);
     let label = def.label(db)?;
     let docs = def.docs(db, famous_defs);
-
     let value = match def {
         Definition::Variant(it) => {
             if !it.parent_enum(db).is_data_carrying(db) {
@@ -462,14 +464,75 @@ pub(super) fn definition(
         _ => None,
     };
 
-    let label = match (value, layout_info) {
-        (Some(value), Some(layout_info)) => format!("{layout_info}\n{label} = {value}"),
-        (Some(value), None) => format!("{label} = {value}"),
-        (None, Some(layout_info)) => format!("{layout_info}\n{label}"),
-        (None, None) => label,
+    let def_ty = match def {
+        Definition::Local(it) => Some(it.ty(db)),
+        Definition::GenericParam(hir::GenericParam::ConstParam(it)) => Some(it.ty(db)),
+        Definition::GenericParam(hir::GenericParam::TypeParam(it)) => Some(it.ty(db)),
+        Definition::Field(field) => Some(field.ty(db)),
+        Definition::TupleField(it) => Some(it.ty(db)),
+        Definition::Function(it) => Some(it.ty(db)),
+        Definition::Adt(it) => Some(it.ty(db)),
+        Definition::Const(it) => Some(it.ty(db)),
+        Definition::Static(it) => Some(it.ty(db)),
+        Definition::TypeAlias(it) => Some(it.ty(db)),
+        Definition::BuiltinType(it) => Some(it.ty(db)),
+        _ => None,
     };
+    let notable_traits = def_ty.and_then(|ty| {
+        let mut desc = String::new();
+        let mut needs_impl_header = true;
+        for &trait_ in db.notable_traits_in_deps(ty.krate(db).into()).iter().flat_map(|it| &**it) {
+            let trait_ = trait_.into();
+            if ty.impls_trait(db, trait_, &[]) {
+                let aliases: Vec<_> = trait_
+                    .items(db)
+                    .into_iter()
+                    .filter_map(AssocItem::as_type_alias)
+                    .map(|alias| (ty.normalize_trait_assoc_type(db, &[], alias), alias.name(db)))
+                    .collect();
+                desc.push_str(if mem::take(&mut needs_impl_header) {
+                    " // notable traits impls: "
+                } else {
+                    ", "
+                });
+                format_to!(desc, "{}", trait_.name(db).display(db),);
+                if !aliases.is_empty() {
+                    desc.push('<');
+                    format_to!(
+                        desc,
+                        "{}",
+                        aliases.into_iter().format_with(", ", |(ty, name), f| {
+                            f(&name.display(db))?;
+                            f(&" = ")?;
+                            match ty {
+                                Some(ty) => f(&ty.display(db)),
+                                None => f(&"?"),
+                            }
+                        })
+                    );
+                    desc.push('>');
+                }
+            }
+        }
+        desc.is_empty().not().then(|| desc)
+    });
+
+    let mut desc = String::new();
+    if let Some(notable_traits) = notable_traits {
+        desc.push_str(&notable_traits);
+        desc.push('\n');
+    }
+    if let Some(layout_info) = layout_info {
+        desc.push_str(&layout_info);
+        desc.push('\n');
+    }
+    desc.push_str(&label);
+    if let Some(value) = value {
+        desc.push_str(" = ");
+        desc.push_str(&value);
+    }
 
-    markup(docs.map(Into::into), label, mod_path)
+    markup(docs.map(Into::into), desc, mod_path)
 }
 
 fn type_info(
diff --git a/crates/ide/src/hover/tests.rs b/crates/ide/src/hover/tests.rs
index 587a6b66c4f..f8693cd1ea6 100644
--- a/crates/ide/src/hover/tests.rs
+++ b/crates/ide/src/hover/tests.rs
@@ -451,16 +451,16 @@ pub fn foo() -> u32 { 1 }
 fn main() { let foo_test = fo$0o(); }
 "#,
         expect![[r#"
-                *foo*
+            *foo*
 
-                ```rust
-                test
-                ```
+            ```rust
+            test
+            ```
 
-                ```rust
-                pub fn foo() -> u32
-                ```
-            "#]],
+            ```rust
+            pub fn foo() -> u32
+            ```
+        "#]],
     );
 
     // Use literal `crate` in path
@@ -495,16 +495,16 @@ mod m { pub fn foo() -> super::X { super::X } }
 fn main() { m::f$0oo(); }
         "#,
         expect![[r#"
-                *foo*
+            *foo*
 
-                ```rust
-                test::m
-                ```
+            ```rust
+            test::m
+            ```
 
-                ```rust
-                pub fn foo() -> super::X
-                ```
-            "#]],
+            ```rust
+            pub fn foo() -> super::X
+            ```
+        "#]],
     );
 }
 
@@ -559,18 +559,18 @@ pub fn foo<'a, T: AsRef<str>>(b: &'a T) -> &'a str { }
 fn main() { let foo_test = fo$0o(); }
         "#,
         expect![[r#"
-                *foo*
+            *foo*
 
-                ```rust
-                test
-                ```
+            ```rust
+            test
+            ```
 
-                ```rust
-                pub fn foo<'a, T>(b: &'a T) -> &'a str
-                where
-                    T: AsRef<str>,
-                ```
-            "#]],
+            ```rust
+            pub fn foo<'a, T>(b: &'a T) -> &'a str
+            where
+                T: AsRef<str>,
+            ```
+        "#]],
     );
 }
 
@@ -583,16 +583,16 @@ pub fn foo$0(a: u32, b: u32) -> u32 {}
 fn main() { }
 "#,
         expect![[r#"
-                *foo*
+            *foo*
 
-                ```rust
-                test
-                ```
+            ```rust
+            test
+            ```
 
-                ```rust
-                pub fn foo(a: u32, b: u32) -> u32
-                ```
-            "#]],
+            ```rust
+            pub fn foo(a: u32, b: u32) -> u32
+            ```
+        "#]],
     );
 }
 
@@ -610,27 +610,27 @@ pub fn foo$0(_: &Path) {}
 
 fn main() { }
 "#,
-        expect![[r##"
-                *foo*
+        expect![[r#"
+            *foo*
 
-                ```rust
-                test
-                ```
+            ```rust
+            test
+            ```
 
-                ```rust
-                pub fn foo(_: &Path)
-                ```
+            ```rust
+            pub fn foo(_: &Path)
+            ```
 
-                ---
+            ---
 
-                # Example
+            # Example
 
-                ```
-                # use std::path::Path;
-                #
-                foo(Path::new("hello, world!"))
-                ```
-            "##]],
+            ```
+            # use std::path::Path;
+            #
+            foo(Path::new("hello, world!"))
+            ```
+        "#]],
     );
 }
 
@@ -643,21 +643,21 @@ pub fn foo$0(_: &Path) {}
 
 fn main() { }
 "##,
-        expect![[r##"
-                *foo*
+        expect![[r#"
+            *foo*
 
-                ```rust
-                test
-                ```
+            ```rust
+            test
+            ```
 
-                ```rust
-                pub fn foo(_: &Path)
-                ```
+            ```rust
+            pub fn foo(_: &Path)
+            ```
 
-                ---
+            ---
 
-                Raw string doc attr
-            "##]],
+            Raw string doc attr
+        "#]],
     );
 }
 
@@ -1040,12 +1040,12 @@ fn hover_for_param_with_multiple_traits() {
             }
             fn f(_x$0: impl Deref<Target=u8> + DerefMut<Target=u8>) {}"#,
         expect![[r#"
-                *_x*
+            *_x*
 
-                ```rust
-                _x: impl Deref<Target = u8> + DerefMut<Target = u8>
-                ```
-            "#]],
+            ```rust
+            _x: impl Deref<Target = u8> + DerefMut<Target = u8>
+            ```
+        "#]],
     )
 }
 
@@ -1087,15 +1087,15 @@ mod wrapper {
 fn main() { let foo_test = wrapper::Thing::new$0(); }
 "#,
         expect![[r#"
-                *new*
+            *new*
 
-                ```rust
-                test::wrapper::Thing
-                ```
+            ```rust
+            test::wrapper::Thing
+            ```
 
-                ```rust
-                pub fn new() -> Thing
-                ```
+            ```rust
+            pub fn new() -> Thing
+            ```
         "#]],
     )
 }
@@ -1299,12 +1299,12 @@ fn test_hover_tuple_field() {
     check(
         r#"struct TS(String, i32$0);"#,
         expect![[r#"
-                *i32*
+            *i32*
 
-                ```rust
-                i32
-                ```
-            "#]],
+            ```rust
+            i32
+            ```
+        "#]],
     )
 }
 
@@ -1319,16 +1319,16 @@ id! {
 }
 "#,
         expect![[r#"
-                *foo*
+            *foo*
 
-                ```rust
-                test
-                ```
+            ```rust
+            test
+            ```
 
-                ```rust
-                fn foo()
-                ```
-            "#]],
+            ```rust
+            fn foo()
+            ```
+        "#]],
     );
 }
 
@@ -1341,16 +1341,16 @@ fn test_hover_through_attr() {
 fn foo$0() {}
 "#,
         expect![[r#"
-                *foo*
+            *foo*
 
-                ```rust
-                test
-                ```
+            ```rust
+            test
+            ```
 
-                ```rust
-                fn foo()
-                ```
-            "#]],
+            ```rust
+            fn foo()
+            ```
+        "#]],
     );
 }
 
@@ -1427,16 +1427,16 @@ fn foo() {
 }
 "#,
         expect![[r#"
-                *bar*
+            *bar*
 
-                ```rust
-                test
-                ```
+            ```rust
+            test
+            ```
 
-                ```rust
-                fn bar() -> bool
-                ```
-            "#]],
+            ```rust
+            fn bar() -> bool
+            ```
+        "#]],
     );
 }
 
@@ -1483,20 +1483,20 @@ fn foo() { }
 fn bar() { fo$0o(); }
 ",
         expect![[r#"
-                *foo*
+            *foo*
 
-                ```rust
-                test
-                ```
+            ```rust
+            test
+            ```
 
-                ```rust
-                fn foo()
-                ```
+            ```rust
+            fn foo()
+            ```
 
-                ---
+            ---
 
-                \<- ` ` here
-            "#]],
+            \<- ` ` here
+        "#]],
     );
 }
 
@@ -1505,45 +1505,45 @@ fn test_hover_function_show_qualifiers() {
     check(
         r#"async fn foo$0() {}"#,
         expect![[r#"
-                *foo*
+            *foo*
 
-                ```rust
-                test
-                ```
+            ```rust
+            test
+            ```
 
-                ```rust
-                async fn foo()
-                ```
-            "#]],
+            ```rust
+            async fn foo()
+            ```
+        "#]],
     );
     check(
         r#"pub const unsafe fn foo$0() {}"#,
         expect![[r#"
-                *foo*
+            *foo*
 
-                ```rust
-                test
-                ```
+            ```rust
+            test
+            ```
 
-                ```rust
-                pub const unsafe fn foo()
-                ```
-            "#]],
+            ```rust
+            pub const unsafe fn foo()
+            ```
+        "#]],
     );
     // Top level `pub(crate)` will be displayed as no visibility.
     check(
         r#"mod m { pub(crate) async unsafe extern "C" fn foo$0() {} }"#,
         expect![[r#"
-                *foo*
+            *foo*
 
-                ```rust
-                test::m
-                ```
+            ```rust
+            test::m
+            ```
 
-                ```rust
-                pub(crate) async unsafe extern "C" fn foo()
-                ```
-            "#]],
+            ```rust
+            pub(crate) async unsafe extern "C" fn foo()
+            ```
+        "#]],
     );
 }
 
@@ -1552,16 +1552,16 @@ fn test_hover_function_show_types() {
     check(
         r#"fn foo$0(a: i32, b:i32) -> i32 { 0 }"#,
         expect![[r#"
-                *foo*
+            *foo*
 
-                ```rust
-                test
-                ```
+            ```rust
+            test
+            ```
 
-                ```rust
-                fn foo(a: i32, b: i32) -> i32
-                ```
-            "#]],
+            ```rust
+            fn foo(a: i32, b: i32) -> i32
+            ```
+        "#]],
     );
 }
 
@@ -1949,39 +1949,39 @@ fn test_hover_no_links() {
 pub fn fo$0o() {}
 "#,
         expect![[r#"
-                *foo*
+            *foo*
 
-                ```rust
-                test
-                ```
+            ```rust
+            test
+            ```
 
-                ```rust
-                pub fn foo()
-                ```
+            ```rust
+            pub fn foo()
+            ```
 
-                ---
+            ---
 
-                Test cases:
-                case 1.  bare URL: https://www.example.com/
-                case 2.  inline URL with title: [example](https://www.example.com/)
-                case 3.  code reference: `Result`
-                case 4.  code reference but miss footnote: `String`
-                case 5.  autolink: http://www.example.com/
-                case 6.  email address: test@example.com
-                case 7.  reference: example
-                case 8.  collapsed link: example
-                case 9.  shortcut link: example
-                case 10. inline without URL: example
-                case 11. reference: foo
-                case 12. reference: foo
-                case 13. collapsed link: foo
-                case 14. shortcut link: foo
-                case 15. inline without URL: foo
-                case 16. just escaped text: \[foo\]
-                case 17. inline link: Foo
-
-                [^example]: https://www.example.com/
-            "#]],
+            Test cases:
+            case 1.  bare URL: https://www.example.com/
+            case 2.  inline URL with title: [example](https://www.example.com/)
+            case 3.  code reference: `Result`
+            case 4.  code reference but miss footnote: `String`
+            case 5.  autolink: http://www.example.com/
+            case 6.  email address: test@example.com
+            case 7.  reference: example
+            case 8.  collapsed link: example
+            case 9.  shortcut link: example
+            case 10. inline without URL: example
+            case 11. reference: foo
+            case 12. reference: foo
+            case 13. collapsed link: foo
+            case 14. shortcut link: foo
+            case 15. inline without URL: foo
+            case 16. just escaped text: \[foo\]
+            case 17. inline link: Foo
+
+            [^example]: https://www.example.com/
+        "#]],
     );
 }
 
@@ -2091,20 +2091,20 @@ bar!();
 fn foo() { let bar = Bar; bar.fo$0o(); }
 "#,
         expect![[r#"
-                *foo*
+            *foo*
 
-                ```rust
-                test::Bar
-                ```
+            ```rust
+            test::Bar
+            ```
 
-                ```rust
-                fn foo(&self)
-                ```
+            ```rust
+            fn foo(&self)
+            ```
 
-                ---
+            ---
 
-                Do the foo
-            "#]],
+            Do the foo
+        "#]],
     );
 }
 
@@ -2129,20 +2129,20 @@ bar!();
 fn foo() { let bar = Bar; bar.fo$0o(); }
 "#,
         expect![[r#"
-                *foo*
+            *foo*
 
-                ```rust
-                test::Bar
-                ```
+            ```rust
+            test::Bar
+            ```
 
-                ```rust
-                fn foo(&self)
-                ```
+            ```rust
+            fn foo(&self)
+            ```
 
-                ---
+            ---
 
-                Do the foo
-            "#]],
+            Do the foo
+        "#]],
     );
 }
 
@@ -3305,7 +3305,7 @@ fn main() { let foo_test = name_with_dashes::wrapper::Thing::new$0(); }
             ```rust
             pub fn new() -> Thing
             ```
-            "#]],
+        "#]],
     )
 }
 
@@ -3327,7 +3327,7 @@ fn main() {
 
             ```rust
              // size = 8, align = 8, niches = 1
-            f: &i32
+            let f: &i32
             ```
             ---
 
@@ -3628,24 +3628,24 @@ fn hover_doc_block_style_indent_end() {
 fn foo$0() {}
 "#,
         expect![[r#"
-                *foo*
+            *foo*
 
-                ```rust
-                test
-                ```
+            ```rust
+            test
+            ```
 
-                ```rust
-                fn foo()
-                ```
+            ```rust
+            fn foo()
+            ```
 
-                ---
+            ---
 
-                foo
+            foo
 
-                ```rust
-                let x = 3;
-                ```
-            "#]],
+            ```rust
+            let x = 3;
+            ```
+        "#]],
     );
 }
 
@@ -3704,12 +3704,12 @@ trait TraitB {}
 impl<T: TraitA + TraitB> Foo<T$0> where T: Sized {}
 "#,
         expect![[r#"
-                *T*
+            *T*
 
-                ```rust
-                T: TraitA + TraitB
-                ```
-            "#]],
+            ```rust
+            T: TraitA + TraitB
+            ```
+        "#]],
     );
     check(
         r#"
@@ -3718,12 +3718,12 @@ struct Foo<T>(T);
 impl<T> Foo<T$0> {}
 "#,
         expect![[r#"
-                *T*
+            *T*
 
-                ```rust
-                T
-                ```
-                "#]],
+            ```rust
+            T
+            ```
+        "#]],
     );
     // lifetimes bounds arent being tracked yet
     check(
@@ -3733,12 +3733,12 @@ struct Foo<T>(T);
 impl<T: 'static> Foo<T$0> {}
 "#,
         expect![[r#"
-                *T*
+            *T*
 
-                ```rust
-                T
-                ```
-                "#]],
+            ```rust
+            T
+            ```
+        "#]],
     );
 }
 
@@ -3753,12 +3753,12 @@ struct Foo<T>(T);
 impl<T: Trait> Foo<T$0> {}
 "#,
         expect![[r#"
-                *T*
+            *T*
 
-                ```rust
-                T: Trait
-                ```
-            "#]],
+            ```rust
+            T: Trait
+            ```
+        "#]],
     );
     check(
         r#"
@@ -3768,12 +3768,12 @@ struct Foo<T>(T);
 impl<T: Trait + ?Sized> Foo<T$0> {}
 "#,
         expect![[r#"
-                *T*
+            *T*
 
-                ```rust
-                T: Trait + ?Sized
-                ```
-            "#]],
+            ```rust
+            T: Trait + ?Sized
+            ```
+        "#]],
     );
 }
 
@@ -3788,12 +3788,12 @@ mod type_param_sized_bounds {
 fn foo<T$0>() {}
 "#,
             expect![[r#"
-                    *T*
+                *T*
 
-                    ```rust
-                    T
-                    ```
-                "#]],
+                ```rust
+                T
+                ```
+            "#]],
         );
     }
 
@@ -3805,12 +3805,12 @@ fn foo<T$0>() {}
 fn foo<T$0: Sized>() {}
 "#,
             expect![[r#"
-                    *T*
+                *T*
 
-                    ```rust
-                    T
-                    ```
-                "#]],
+                ```rust
+                T
+                ```
+            "#]],
         );
     }
 
@@ -3822,12 +3822,12 @@ fn foo<T$0: Sized>() {}
 fn foo<T$0: ?Sized>() {}
 "#,
             expect![[r#"
-                    *T*
+                *T*
 
-                    ```rust
-                    T: ?Sized
-                    ```
-                "#]],
+                ```rust
+                T: ?Sized
+                ```
+            "#]],
         );
     }
 
@@ -3840,12 +3840,12 @@ trait Trait {}
 fn foo<T$0: Trait>() {}
 "#,
             expect![[r#"
-                    *T*
+                *T*
 
-                    ```rust
-                    T: Trait
-                    ```
-                "#]],
+                ```rust
+                T: Trait
+                ```
+            "#]],
         );
     }
 
@@ -3858,12 +3858,12 @@ trait Trait {}
 fn foo<T$0: Trait + Sized>() {}
 "#,
             expect![[r#"
-                    *T*
+                *T*
 
-                    ```rust
-                    T: Trait
-                    ```
-                "#]],
+                ```rust
+                T: Trait
+                ```
+            "#]],
         );
     }
 
@@ -3876,12 +3876,12 @@ trait Trait {}
 fn foo<T$0: Trait + ?Sized>() {}
 "#,
             expect![[r#"
-                    *T*
+                *T*
 
-                    ```rust
-                    T: Trait + ?Sized
-                    ```
-                "#]],
+                ```rust
+                T: Trait + ?Sized
+                ```
+            "#]],
         );
     }
 
@@ -3893,12 +3893,12 @@ fn foo<T$0: Trait + ?Sized>() {}
 fn foo<T$0: ?Sized + Sized + Sized>() {}
 "#,
             expect![[r#"
-                    *T*
+                *T*
 
-                    ```rust
-                    T
-                    ```
-                "#]],
+                ```rust
+                T
+                ```
+            "#]],
         );
         check(
             r#"
@@ -3907,12 +3907,12 @@ trait Trait {}
 fn foo<T$0: Sized + ?Sized + Sized + Trait>() {}
 "#,
             expect![[r#"
-                    *T*
+                *T*
 
-                    ```rust
-                    T: Trait
-                    ```
-                "#]],
+                ```rust
+                T: Trait
+                ```
+            "#]],
         );
     }
 }
@@ -3947,12 +3947,12 @@ struct Foo<const LEN: usize>;
 impl<const LEN: usize> Foo<LEN$0> {}
 "#,
         expect![[r#"
-                *LEN*
+            *LEN*
 
-                ```rust
-                const LEN: usize
-                ```
-            "#]],
+            ```rust
+            const LEN: usize
+            ```
+        "#]],
     );
 }
 
@@ -4082,20 +4082,20 @@ fn main() {
 }
 "#,
         expect![[r#"
-        *B*
+            *B*
 
-        ```rust
-        test
-        ```
+            ```rust
+            test
+            ```
 
-        ```rust
-        const B: bool = true
-        ```
+            ```rust
+            const B: bool = true
+            ```
 
-        ---
+            ---
 
-        true
-    "#]],
+            true
+        "#]],
     );
 
     check(
@@ -4119,16 +4119,16 @@ fn main() {
 }
 "#,
         expect![[r#"
-        *AA*
+            *AA*
 
-        ```rust
-        test
-        ```
+            ```rust
+            test
+            ```
 
-        ```rust
-        const AA: A = A { i: 5 }
-        ```
-    "#]],
+            ```rust
+            const AA: A = A { i: 5 }
+            ```
+        "#]],
     );
 
     check(
@@ -5045,17 +5045,17 @@ const _: &str$0 = ""; }
 mod prim_str {}
 "#,
         expect![[r#"
-                *str*
+            *str*
 
-                ```rust
-                str
-                ```
+            ```rust
+            str
+            ```
 
-                ---
+            ---
 
-                Docs for prim_str
-                [`foo`](https://doc.rust-lang.org/nightly/std/keyword.foo.html)
-            "#]],
+            Docs for prim_str
+            [`foo`](https://doc.rust-lang.org/nightly/std/keyword.foo.html)
+        "#]],
     );
 }
 
@@ -5081,20 +5081,20 @@ fn main() {
 }
 "#,
         expect![[r#"
-                *bar*
+            *bar*
 
-                ```rust
-                test
-                ```
+            ```rust
+            test
+            ```
 
-                ```rust
-                fn bar<'t, T>(s: &mut S<'t, T>, t: u32) -> *mut u32
-                where
-                    T: Clone + 't,
-                    't: 't + 't,
-                    for<'a> T: Clone + 'a,
-                ```
-            "#]],
+            ```rust
+            fn bar<'t, T>(s: &mut S<'t, T>, t: u32) -> *mut u32
+            where
+                T: Clone + 't,
+                't: 't + 't,
+                for<'a> T: Clone + 'a,
+            ```
+        "#]],
     )
 }
 
@@ -5164,16 +5164,16 @@ impl T1 for Foo {
 }
         "#,
         expect![[r#"
-*Bar*
+            *Bar*
 
-```rust
-test::t2
-```
+            ```rust
+            test::t2
+            ```
 
-```rust
-pub type Bar
-```
-"#]],
+            ```rust
+            pub type Bar
+            ```
+        "#]],
     );
 }
 #[test]
@@ -5186,16 +5186,16 @@ trait A {
     type Assoc;
 }"#,
         expect![[r#"
-                *Assoc*
+            *Assoc*
 
-                ```rust
-                test
-                ```
+            ```rust
+            test
+            ```
 
-                ```rust
-                type Assoc
-                ```
-            "#]],
+            ```rust
+            type Assoc
+            ```
+        "#]],
     );
     check(
         r#"
@@ -5207,16 +5207,16 @@ trait A {
     type Assoc;
 }"#,
         expect![[r#"
-                *Assoc*
+            *Assoc*
 
-                ```rust
-                test
-                ```
+            ```rust
+            test
+            ```
 
-                ```rust
-                type Assoc
-                ```
-            "#]],
+            ```rust
+            type Assoc
+            ```
+        "#]],
     );
     check(
         r#"
@@ -5226,16 +5226,16 @@ trait A where
     type Assoc;
 }"#,
         expect![[r#"
-                *Assoc*
+            *Assoc*
 
-                ```rust
-                test
-                ```
+            ```rust
+            test
+            ```
 
-                ```rust
-                type Assoc
-                ```
-            "#]],
+            ```rust
+            type Assoc
+            ```
+        "#]],
     );
 }
 
@@ -7059,3 +7059,63 @@ fn main() {
         "#]],
     );
 }
+
+#[test]
+fn notable_local() {
+    check(
+        r#"
+#[doc(notable_trait)]
+trait Notable {
+    type Assoc;
+    type Assoc2;
+}
+
+impl Notable for u32 {
+    type Assoc = &str;
+    type Assoc2 = char;
+}
+fn main(notable$0: u32) {}
+"#,
+        expect![[r#"
+            *notable*
+
+            ```rust
+             // notable traits impls: Notable<Assoc = &str, Assoc2 = char>
+             // size = 4, align = 4
+            notable: u32
+            ```
+        "#]],
+    );
+}
+
+#[test]
+fn notable_foreign() {
+    check(
+        r#"
+//- minicore: future, iterator
+struct S;
+#[doc(notable_trait)]
+trait Notable {}
+impl Notable for S$0 {}
+impl core::future::Future for S {
+    type Output = u32;
+}
+impl Iterator for S {
+    type Item = S;
+}
+"#,
+        expect![[r#"
+            *S*
+
+            ```rust
+            test
+            ```
+
+            ```rust
+             // notable traits impls: Notable, Future<Output = u32>, Iterator<Item = S>
+             // size = 0, align = 1
+            struct S
+            ```
+        "#]],
+    );
+}
diff --git a/crates/test-utils/src/minicore.rs b/crates/test-utils/src/minicore.rs
index 3b836dfcd9a..b015dd69b52 100644
--- a/crates/test-utils/src/minicore.rs
+++ b/crates/test-utils/src/minicore.rs
@@ -1166,6 +1166,7 @@ pub mod future {
         task::{Context, Poll},
     };
 
+    #[doc(notable_trait)]
     #[lang = "future_trait"]
     pub trait Future {
         type Output;
@@ -1264,6 +1265,7 @@ pub mod iter {
 
     mod traits {
         mod iterator {
+            #[doc(notable_trait)]
             pub trait Iterator {
                 type Item;
                 #[lang = "next"]