Repository: rust-lang/rust-analyzer (Rust)

Issue:
Support local trait impls for non-local types for unnamed free constants
In following code: 

```rust
trait Foo {}
struct A {}

const _ : () = {
    impl crate::Foo for crate::A {}  <---  (1)
};
```

(1) is local trait impls for non-local types. Currently we do not support it due to the fact that we do not want to look at the bodies of all functions and blocks. But is it possible to support this case solely, as an escape hatch for proc-macro author to workaround of lacking of proc-macro hygiene at the moment  ?

What do you think about it @jonas-schievink  ?



Discussion:
Yeah, I think this is a good idea!
If there's any `use` items in the initializer, then this might need better support for inner items first.

Then we have to stop ignoring unnamed consts here and record them as part of the containing `DefMap`, like we do for other items:

https://github.com/rust-analyzer/rust-analyzer/blob/700a3d5d75cdc1b8b7049d35af335c638e079c5c/crates/hir_def/src/nameres/collector.rs#L986

Finally, we have to change the `trait_impls_in_crate` query to inspect the body of all anonymous consts to look for blocks containing impls (this might need further changes to body lowering first):

https://github.com/rust-analyzer/rust-analyzer/blob/6bde542a39fe63298a31b838e59705797ed8a2cf/crates/ra_hir_ty/src/method_resolution.rs#L49

It is possible that there's a more optimal way of doing this though, but this should be a start.
Maybe we should even restrict it to unnamed consts of unit type? That would be annoying to check during name resolution though.
We can check for a literal `()` type, ignoring type aliases and associated types. Not sure if that's worth it though, anonymous consts might be used rarely enough where it doesn't matter if we always do it.
Actually, thinking about it, I can't think of any reason to make an anonymous const with a different type, so it probably wouldn't help anyway.



Patch:
diff --git a/crates/hir_def/src/item_scope.rs b/crates/hir_def/src/item_scope.rs
index a8ee5eeac9f5..9014468ea04f 100644
--- a/crates/hir_def/src/item_scope.rs
+++ b/crates/hir_def/src/item_scope.rs
@@ -11,7 +11,7 @@ use rustc_hash::{FxHashMap, FxHashSet};
 use stdx::format_to;
 
 use crate::{
-    db::DefDatabase, per_ns::PerNs, visibility::Visibility, AdtId, BuiltinType, ImplId,
+    db::DefDatabase, per_ns::PerNs, visibility::Visibility, AdtId, BuiltinType, ConstId, ImplId,
     LocalModuleId, MacroDefId, ModuleDefId, ModuleId, TraitId,
 };
 
@@ -37,6 +37,7 @@ pub struct ItemScope {
 
     defs: Vec<ModuleDefId>,
     impls: Vec<ImplId>,
+    unnamed_consts: Vec<ConstId>,
     /// Traits imported via `use Trait as _;`.
     unnamed_trait_imports: FxHashMap<TraitId, Visibility>,
     /// Macros visible in current module in legacy textual scope
@@ -106,6 +107,10 @@ impl ItemScope {
             .map(|(_, v)| v)
     }
 
+    pub fn unnamed_consts(&self) -> impl Iterator<Item = ConstId> + '_ {
+        self.unnamed_consts.iter().copied()
+    }
+
     /// Iterate over all module scoped macros
     pub(crate) fn macros<'a>(&'a self) -> impl Iterator<Item = (&'a Name, MacroDefId)> + 'a {
         self.entries().filter_map(|(name, def)| def.take_macros().map(|macro_| (name, macro_)))
@@ -156,6 +161,10 @@ impl ItemScope {
         self.impls.push(imp)
     }
 
+    pub(crate) fn define_unnamed_const(&mut self, konst: ConstId) {
+        self.unnamed_consts.push(konst);
+    }
+
     pub(crate) fn define_legacy_macro(&mut self, name: Name, mac: MacroDefId) {
         self.legacy_macros.insert(name, mac);
     }
@@ -295,6 +304,7 @@ impl ItemScope {
             unresolved,
             defs,
             impls,
+            unnamed_consts,
             unnamed_trait_imports,
             legacy_macros,
         } = self;
@@ -304,6 +314,7 @@ impl ItemScope {
         unresolved.shrink_to_fit();
         defs.shrink_to_fit();
         impls.shrink_to_fit();
+        unnamed_consts.shrink_to_fit();
         unnamed_trait_imports.shrink_to_fit();
         legacy_macros.shrink_to_fit();
     }
diff --git a/crates/hir_def/src/nameres/collector.rs b/crates/hir_def/src/nameres/collector.rs
index f42f9270230b..492d8c71ff83 100644
--- a/crates/hir_def/src/nameres/collector.rs
+++ b/crates/hir_def/src/nameres/collector.rs
@@ -1163,19 +1163,27 @@ impl ModCollector<'_, '_> {
                 }
                 ModItem::Const(id) => {
                     let it = &self.item_tree[id];
-
-                    if let Some(name) = &it.name {
-                        def = Some(DefData {
-                            id: ConstLoc {
-                                container: module.into(),
-                                id: ItemTreeId::new(self.file_id, id),
-                            }
-                            .intern(self.def_collector.db)
-                            .into(),
-                            name,
-                            visibility: &self.item_tree[it.visibility],
-                            has_constructor: false,
-                        });
+                    let const_id = ConstLoc {
+                        container: module.into(),
+                        id: ItemTreeId::new(self.file_id, id),
+                    }
+                    .intern(self.def_collector.db);
+
+                    match &it.name {
+                        Some(name) => {
+                            def = Some(DefData {
+                                id: const_id.into(),
+                                name,
+                                visibility: &self.item_tree[it.visibility],
+                                has_constructor: false,
+                            });
+                        }
+                        None => {
+                            // const _: T = ...;
+                            self.def_collector.def_map.modules[self.module_id]
+                                .scope
+                                .define_unnamed_const(const_id);
+                        }
                     }
                 }
                 ModItem::Static(id) => {
diff --git a/crates/hir_ty/src/method_resolution.rs b/crates/hir_ty/src/method_resolution.rs
index ee725fd468bb..f29319f2065a 100644
--- a/crates/hir_ty/src/method_resolution.rs
+++ b/crates/hir_ty/src/method_resolution.rs
@@ -8,8 +8,8 @@ use arrayvec::ArrayVec;
 use base_db::CrateId;
 use chalk_ir::{cast::Cast, Mutability, UniverseIndex};
 use hir_def::{
-    lang_item::LangItemTarget, AssocContainerId, AssocItemId, FunctionId, GenericDefId, HasModule,
-    ImplId, Lookup, ModuleId, TraitId,
+    lang_item::LangItemTarget, nameres::DefMap, AssocContainerId, AssocItemId, FunctionId,
+    GenericDefId, HasModule, ImplId, Lookup, ModuleId, TraitId,
 };
 use hir_expand::name::Name;
 use rustc_hash::{FxHashMap, FxHashSet};
@@ -100,25 +100,38 @@ impl TraitImpls {
         let mut impls = Self { map: FxHashMap::default() };
 
         let crate_def_map = db.crate_def_map(krate);
-        for (_module_id, module_data) in crate_def_map.modules() {
-            for impl_id in module_data.scope.impls() {
-                let target_trait = match db.impl_trait(impl_id) {
-                    Some(tr) => tr.skip_binders().hir_trait_id(),
-                    None => continue,
-                };
-                let self_ty = db.impl_self_ty(impl_id);
-                let self_ty_fp = TyFingerprint::for_impl(self_ty.skip_binders());
-                impls
-                    .map
-                    .entry(target_trait)
-                    .or_default()
-                    .entry(self_ty_fp)
-                    .or_default()
-                    .push(impl_id);
+        collect_def_map(db, &crate_def_map, &mut impls);
+
+        return Arc::new(impls);
+
+        fn collect_def_map(db: &dyn HirDatabase, def_map: &DefMap, impls: &mut TraitImpls) {
+            for (_module_id, module_data) in def_map.modules() {
+                for impl_id in module_data.scope.impls() {
+                    let target_trait = match db.impl_trait(impl_id) {
+                        Some(tr) => tr.skip_binders().hir_trait_id(),
+                        None => continue,
+                    };
+                    let self_ty = db.impl_self_ty(impl_id);
+                    let self_ty_fp = TyFingerprint::for_impl(self_ty.skip_binders());
+                    impls
+                        .map
+                        .entry(target_trait)
+                        .or_default()
+                        .entry(self_ty_fp)
+                        .or_default()
+                        .push(impl_id);
+                }
+
+                // To better support custom derives, collect impls in all unnamed const items.
+                // const _: () = { ... };
+                for konst in module_data.scope.unnamed_consts() {
+                    let body = db.body(konst.into());
+                    for (_, block_def_map) in body.blocks(db.upcast()) {
+                        collect_def_map(db, &block_def_map, impls);
+                    }
+                }
             }
         }
-
-        Arc::new(impls)
     }
 
     pub(crate) fn trait_impls_in_deps_query(db: &dyn HirDatabase, krate: CrateId) -> Arc<Self> {
@@ -208,6 +221,9 @@ impl InherentImpls {
             }
         }
 
+        // NOTE: We're not collecting inherent impls from unnamed consts here, we intentionally only
+        // support trait impls there.
+
         Arc::new(Self { map })
     }
 


Test patch:
diff --git a/crates/hir_ty/src/tests/method_resolution.rs b/crates/hir_ty/src/tests/method_resolution.rs
index 61f18b0d2069..4b2c82b417fe 100644
--- a/crates/hir_ty/src/tests/method_resolution.rs
+++ b/crates/hir_ty/src/tests/method_resolution.rs
@@ -1292,3 +1292,25 @@ mod b {
         "#]],
     )
 }
+
+#[test]
+fn impl_in_unnamed_const() {
+    check_types(
+        r#"
+struct S;
+
+trait Tr {
+    fn method(&self) -> u16;
+}
+
+const _: () = {
+    impl Tr for S {}
+};
+
+fn f() {
+    S.method();
+  //^^^^^^^^^^ u16
+}
+    "#,
+    );
+}