Repository: rust-lang/rust-analyzer
Pull request #19375: feat: Allow crate authors to control completion of their things
Author: login: ChayimFriedman2
Labels: name: S-waiting-on-review
description: Status: Awaiting review from the assignee but also interested parties.
Created: 2025-03-16T11:25:23Z | Merged: 2025-03-28T09:23:01Z

Via the new `#[rust_analyzer::completions(...)]` attribute.

Closes https://github.com/rust-lang/rust-analyzer/issues/17477.

Also fix a bug with existing settings for that where the paths wouldn't resolve correctly. @Veykril it seems you just blessed the tests incorrectly in https://github.com/rust-lang/rust-analyzer/pull/18179?


Diff:
diff --git a/crates/hir-def/src/data.rs b/crates/hir-def/src/data.rs
index b251564016c7..e1fe13f9af05 100644
--- a/crates/hir-def/src/data.rs
+++ b/crates/hir-def/src/data.rs
@@ -12,7 +12,7 @@ use crate::{
     ConstId, ExternCrateId, FunctionId, HasModule, ImplId, ItemContainerId, ItemLoc, Lookup,
     Macro2Id, MacroRulesId, ProcMacroId, StaticId, TraitAliasId, TraitId, TypeAliasId,
     db::DefDatabase,
-    item_tree::{self, FnFlags, ModItem},
+    item_tree::{self, FnFlags, ModItem, StaticFlags},
     nameres::proc_macro::{ProcMacroKind, parse_macro_name_and_helper_attrs},
     path::ImportAlias,
     type_ref::{TraitRef, TypeBound, TypeRefId, TypesMap},
@@ -27,9 +27,8 @@ pub struct FunctionData {
     pub visibility: RawVisibility,
     pub abi: Option<Symbol>,
     pub legacy_const_generics_indices: Option<Box<Box<[u32]>>>,
-    pub rustc_allow_incoherent_impl: bool,
     pub types_map: Arc<TypesMap>,
-    flags: FnFlags,
+    pub flags: FnFlags,
 }
 
 impl FunctionData {
@@ -72,7 +71,9 @@ impl FunctionData {
         }
 
         let attrs = item_tree.attrs(db, krate, ModItem::from(loc.id.value).into());
-        let rustc_allow_incoherent_impl = attrs.by_key(&sym::rustc_allow_incoherent_impl).exists();
+        if attrs.by_key(&sym::rustc_allow_incoherent_impl).exists() {
+            flags |= FnFlags::RUSTC_ALLOW_INCOHERENT_IMPL;
+        }
         if flags.contains(FnFlags::HAS_UNSAFE_KW)
             && attrs.by_key(&sym::rustc_deprecated_safe_2024).exists()
         {
@@ -101,51 +102,65 @@ impl FunctionData {
             legacy_const_generics_indices: attrs.rustc_legacy_const_generics(),
             types_map: func.types_map.clone(),
             flags,
-            rustc_allow_incoherent_impl,
         })
     }
 
+    #[inline]
     pub fn has_body(&self) -> bool {
         self.flags.contains(FnFlags::HAS_BODY)
     }
 
     /// True if the first param is `self`. This is relevant to decide whether this
     /// can be called as a method.
+    #[inline]
     pub fn has_self_param(&self) -> bool {
         self.flags.contains(FnFlags::HAS_SELF_PARAM)
     }
 
+    #[inline]
     pub fn is_default(&self) -> bool {
         self.flags.contains(FnFlags::HAS_DEFAULT_KW)
     }
 
+    #[inline]
     pub fn is_const(&self) -> bool {
         self.flags.contains(FnFlags::HAS_CONST_KW)
     }
 
+    #[inline]
     pub fn is_async(&self) -> bool {
         self.flags.contains(FnFlags::HAS_ASYNC_KW)
     }
 
+    #[inline]
     pub fn is_unsafe(&self) -> bool {
         self.flags.contains(FnFlags::HAS_UNSAFE_KW)
     }
 
+    #[inline]
     pub fn is_deprecated_safe_2024(&self) -> bool {
         self.flags.contains(FnFlags::DEPRECATED_SAFE_2024)
     }
 
+    #[inline]
     pub fn is_safe(&self) -> bool {
         self.flags.contains(FnFlags::HAS_SAFE_KW)
     }
 
+    #[inline]
     pub fn is_varargs(&self) -> bool {
         self.flags.contains(FnFlags::IS_VARARGS)
     }
 
+    #[inline]
     pub fn has_target_feature(&self) -> bool {
         self.flags.contains(FnFlags::HAS_TARGET_FEATURE)
     }
+
+    #[inline]
+    pub fn rustc_allow_incoherent_impl(&self) -> bool {
+        self.flags.contains(FnFlags::RUSTC_ALLOW_INCOHERENT_IMPL)
+    }
 }
 
 #[derive(Debug, Clone, PartialEq, Eq)]
@@ -153,15 +168,37 @@ pub struct TypeAliasData {
     pub name: Name,
     pub type_ref: Option<TypeRefId>,
     pub visibility: RawVisibility,
-    pub is_extern: bool,
-    pub rustc_has_incoherent_inherent_impls: bool,
-    pub rustc_allow_incoherent_impl: bool,
+    pub flags: TypeAliasFlags,
     /// Bounds restricting the type alias itself (eg. `type Ty: Bound;` in a trait or impl).
     pub bounds: Box<[TypeBound]>,
     pub types_map: Arc<TypesMap>,
 }
 
+bitflags::bitflags! {
+    #[derive(Debug, Clone, PartialEq, Eq)]
+    pub struct TypeAliasFlags: u8 {
+        const IS_EXTERN = 1 << 0;
+        const RUSTC_HAS_INCOHERENT_INHERENT_IMPLS = 1 << 1;
+        const RUSTC_ALLOW_INCOHERENT_IMPL = 1 << 2;
+    }
+}
+
 impl TypeAliasData {
+    #[inline]
+    pub fn is_extern(&self) -> bool {
+        self.flags.contains(TypeAliasFlags::IS_EXTERN)
+    }
+
+    #[inline]
+    pub fn rustc_has_incoherent_inherent_impls(&self) -> bool {
+        self.flags.contains(TypeAliasFlags::RUSTC_HAS_INCOHERENT_INHERENT_IMPLS)
+    }
+
+    #[inline]
+    pub fn rustc_allow_incoherent_impl(&self) -> bool {
+        self.flags.contains(TypeAliasFlags::RUSTC_ALLOW_INCOHERENT_IMPL)
+    }
+
     pub(crate) fn type_alias_data_query(
         db: &dyn DefDatabase,
         typ: TypeAliasId,
@@ -180,17 +217,24 @@ impl TypeAliasData {
             loc.container.module(db).krate(),
             ModItem::from(loc.id.value).into(),
         );
-        let rustc_has_incoherent_inherent_impls =
-            attrs.by_key(&sym::rustc_has_incoherent_inherent_impls).exists();
-        let rustc_allow_incoherent_impl = attrs.by_key(&sym::rustc_allow_incoherent_impl).exists();
+
+        let mut flags = TypeAliasFlags::empty();
+
+        if matches!(loc.container, ItemContainerId::ExternBlockId(_)) {
+            flags |= TypeAliasFlags::IS_EXTERN;
+        }
+        if attrs.by_key(&sym::rustc_has_incoherent_inherent_impls).exists() {
+            flags |= TypeAliasFlags::RUSTC_HAS_INCOHERENT_INHERENT_IMPLS;
+        }
+        if attrs.by_key(&sym::rustc_allow_incoherent_impl).exists() {
+            flags |= TypeAliasFlags::RUSTC_ALLOW_INCOHERENT_IMPL;
+        }
 
         Arc::new(TypeAliasData {
             name: typ.name.clone(),
             type_ref: typ.type_ref,
             visibility,
-            is_extern: matches!(loc.container, ItemContainerId::ExternBlockId(_)),
-            rustc_has_incoherent_inherent_impls,
-            rustc_allow_incoherent_impl,
+            flags,
             bounds: typ.bounds.clone(),
             types_map: typ.types_map.clone(),
         })
@@ -199,7 +243,7 @@ impl TypeAliasData {
 
 bitflags::bitflags! {
     #[derive(Debug, Clone, Copy, Eq, PartialEq, Default)]
-    pub struct TraitFlags: u8 {
+    pub struct TraitFlags: u16 {
         const IS_AUTO = 1 << 0;
         const IS_UNSAFE = 1 << 1;
         const IS_FUNDAMENTAL = 1 << 2;
@@ -332,9 +376,9 @@ impl Macro2Data {
         let loc = makro.lookup(db);
         let item_tree = loc.id.item_tree(db);
         let makro = &item_tree[loc.id.value];
+        let attrs = item_tree.attrs(db, loc.container.krate(), ModItem::from(loc.id.value).into());
 
-        let helpers = item_tree
-            .attrs(db, loc.container.krate(), ModItem::from(loc.id.value).into())
+        let helpers = attrs
             .by_key(&sym::rustc_builtin_macro)
             .tt_values()
             .next()
@@ -362,11 +406,9 @@ impl MacroRulesData {
         let loc = makro.lookup(db);
         let item_tree = loc.id.item_tree(db);
         let makro = &item_tree[loc.id.value];
+        let attrs = item_tree.attrs(db, loc.container.krate(), ModItem::from(loc.id.value).into());
 
-        let macro_export = item_tree
-            .attrs(db, loc.container.krate(), ModItem::from(loc.id.value).into())
-            .by_key(&sym::macro_export)
-            .exists();
+        let macro_export = attrs.by_key(&sym::macro_export).exists();
 
         Arc::new(MacroRulesData { name: makro.name.clone(), macro_export })
     }
@@ -387,11 +429,9 @@ impl ProcMacroData {
         let loc = makro.lookup(db);
         let item_tree = loc.id.item_tree(db);
         let makro = &item_tree[loc.id.value];
+        let attrs = item_tree.attrs(db, loc.container.krate(), ModItem::from(loc.id.value).into());
 
-        let (name, helpers) = if let Some(def) = item_tree
-            .attrs(db, loc.container.krate(), ModItem::from(loc.id.value).into())
-            .parse_proc_macro_decl(&makro.name)
-        {
+        let (name, helpers) = if let Some(def) = attrs.parse_proc_macro_decl(&makro.name) {
             (
                 def.name,
                 match def.kind {
@@ -404,6 +444,7 @@ impl ProcMacroData {
             stdx::never!("proc macro declaration is not a proc macro");
             (makro.name.clone(), None)
         };
+
         Arc::new(ProcMacroData { name, helpers })
     }
 }
@@ -450,9 +491,16 @@ pub struct ConstData {
     pub name: Option<Name>,
     pub type_ref: TypeRefId,
     pub visibility: RawVisibility,
-    pub rustc_allow_incoherent_impl: bool,
-    pub has_body: bool,
     pub types_map: Arc<TypesMap>,
+    pub flags: ConstFlags,
+}
+
+bitflags::bitflags! {
+    #[derive(Debug, Clone, Copy, PartialEq, Eq)]
+    pub struct ConstFlags: u8 {
+        const RUSTC_ALLOW_INCOHERENT_IMPL = 1 << 0;
+        const HAS_BODY = 1 << 1;
+    }
 }
 
 impl ConstData {
@@ -465,21 +513,38 @@ impl ConstData {
         } else {
             item_tree[konst.visibility].clone()
         };
+        let attrs = item_tree.attrs(
+            db,
+            loc.container.module(db).krate(),
+            ModItem::from(loc.id.value).into(),
+        );
 
-        let rustc_allow_incoherent_impl = item_tree
-            .attrs(db, loc.container.module(db).krate(), ModItem::from(loc.id.value).into())
-            .by_key(&sym::rustc_allow_incoherent_impl)
-            .exists();
+        let mut flags = ConstFlags::empty();
+        if attrs.by_key(&sym::rustc_allow_incoherent_impl).exists() {
+            flags |= ConstFlags::RUSTC_ALLOW_INCOHERENT_IMPL;
+        }
+        if konst.has_body {
+            flags |= ConstFlags::HAS_BODY;
+        }
 
         Arc::new(ConstData {
             name: konst.name.clone(),
             type_ref: konst.type_ref,
             visibility,
-            rustc_allow_incoherent_impl,
-            has_body: konst.has_body,
+            flags,
             types_map: konst.types_map.clone(),
         })
     }
+
+    #[inline]
+    pub fn rustc_allow_incoherent_impl(&self) -> bool {
+        self.flags.contains(ConstFlags::RUSTC_ALLOW_INCOHERENT_IMPL)
+    }
+
+    #[inline]
+    pub fn has_body(&self) -> bool {
+        self.flags.contains(ConstFlags::HAS_BODY)
+    }
 }
 
 #[derive(Debug, Clone, PartialEq, Eq)]
@@ -487,11 +552,8 @@ pub struct StaticData {
     pub name: Name,
     pub type_ref: TypeRefId,
     pub visibility: RawVisibility,
-    pub mutable: bool,
-    pub is_extern: bool,
-    pub has_safe_kw: bool,
-    pub has_unsafe_kw: bool,
     pub types_map: Arc<TypesMap>,
+    pub flags: StaticFlags,
 }
 
 impl StaticData {
@@ -500,17 +562,39 @@ impl StaticData {
         let item_tree = loc.id.item_tree(db);
         let statik = &item_tree[loc.id.value];
 
+        let mut flags = statik.flags;
+        if matches!(loc.container, ItemContainerId::ExternBlockId(_)) {
+            flags |= StaticFlags::IS_EXTERN;
+        }
+
         Arc::new(StaticData {
             name: statik.name.clone(),
             type_ref: statik.type_ref,
             visibility: item_tree[statik.visibility].clone(),
-            mutable: statik.mutable,
-            is_extern: matches!(loc.container, ItemContainerId::ExternBlockId(_)),
-            has_safe_kw: statik.has_safe_kw,
-            has_unsafe_kw: statik.has_unsafe_kw,
+            flags,
             types_map: statik.types_map.clone(),
         })
     }
+
+    #[inline]
+    pub fn is_extern(&self) -> bool {
+        self.flags.contains(StaticFlags::IS_EXTERN)
+    }
+
+    #[inline]
+    pub fn mutable(&self) -> bool {
+        self.flags.contains(StaticFlags::MUTABLE)
+    }
+
+    #[inline]
+    pub fn has_safe_kw(&self) -> bool {
+        self.flags.contains(StaticFlags::HAS_SAFE_KW)
+    }
+
+    #[inline]
+    pub fn has_unsafe_kw(&self) -> bool {
+        self.flags.contains(StaticFlags::HAS_UNSAFE_KW)
+    }
 }
 
 fn trait_vis(db: &dyn DefDatabase, trait_id: TraitId) -> RawVisibility {
diff --git a/crates/hir-def/src/data/adt.rs b/crates/hir-def/src/data/adt.rs
index e470fe689b91..a54d7663ccac 100644
--- a/crates/hir-def/src/data/adt.rs
+++ b/crates/hir-def/src/data/adt.rs
@@ -226,6 +226,7 @@ impl StructData {
         let repr = repr_from_value(db, krate, &item_tree, ModItem::from(loc.id.value).into());
         let attrs = item_tree.attrs(db, krate, ModItem::from(loc.id.value).into());
         let mut flags = StructFlags::NO_FLAGS;
+
         if attrs.by_key(&sym::rustc_has_incoherent_inherent_impls).exists() {
             flags |= StructFlags::IS_RUSTC_HAS_INCOHERENT_INHERENT_IMPL;
         }
@@ -290,10 +291,10 @@ impl EnumData {
         let krate = loc.container.krate;
         let item_tree = loc.id.item_tree(db);
         let repr = repr_from_value(db, krate, &item_tree, ModItem::from(loc.id.value).into());
-        let rustc_has_incoherent_inherent_impls = item_tree
-            .attrs(db, loc.container.krate, ModItem::from(loc.id.value).into())
-            .by_key(&sym::rustc_has_incoherent_inherent_impls)
-            .exists();
+        let attrs = item_tree.attrs(db, loc.container.krate, ModItem::from(loc.id.value).into());
+
+        let rustc_has_incoherent_inherent_impls =
+            attrs.by_key(&sym::rustc_has_incoherent_inherent_impls).exists();
 
         let enum_ = &item_tree[loc.id.value];
 
diff --git a/crates/hir-def/src/import_map.rs b/crates/hir-def/src/import_map.rs
index 717566f9d796..b1622eaf8373 100644
--- a/crates/hir-def/src/import_map.rs
+++ b/crates/hir-def/src/import_map.rs
@@ -9,11 +9,11 @@ use itertools::Itertools;
 use rustc_hash::FxHashSet;
 use smallvec::SmallVec;
 use span::Edition;
-use stdx::{TupleExt, format_to};
+use stdx::format_to;
 use triomphe::Arc;
 
 use crate::{
-    AssocItemId, FxIndexMap, ModuleDefId, ModuleId, TraitId,
+    AssocItemId, AttrDefId, Complete, FxIndexMap, ModuleDefId, ModuleId, TraitId,
     db::DefDatabase,
     item_scope::{ImportOrExternCrate, ItemInNs},
     nameres::DefMap,
@@ -31,6 +31,8 @@ pub struct ImportInfo {
     pub is_doc_hidden: bool,
     /// Whether this item is annotated with `#[unstable(..)]`.
     pub is_unstable: bool,
+    /// The value of `#[rust_analyzer::completions(...)]`, if exists.
+    pub complete: Complete,
 }
 
 /// A map from publicly exported items to its name.
@@ -172,16 +174,22 @@ impl ImportMap {
                             ItemInNs::Macros(id) => Some(id.into()),
                         }
                     };
-                    let (is_doc_hidden, is_unstable) = attr_id.map_or((false, false), |attr_id| {
-                        let attrs = db.attrs(attr_id);
-                        (attrs.has_doc_hidden(), attrs.is_unstable())
-                    });
+                    let (is_doc_hidden, is_unstable, do_not_complete) = match attr_id {
+                        None => (false, false, Complete::Yes),
+                        Some(attr_id) => {
+                            let attrs = db.attrs(attr_id);
+                            let do_not_complete =
+                                Complete::extract(matches!(attr_id, AttrDefId::TraitId(_)), &attrs);
+                            (attrs.has_doc_hidden(), attrs.is_unstable(), do_not_complete)
+                        }
+                    };
 
                     let import_info = ImportInfo {
                         name: name.clone(),
                         container: module,
                         is_doc_hidden,
                         is_unstable,
+                        complete: do_not_complete,
                     };
 
                     if let Some(ModuleDefId::TraitId(tr)) = item.as_module_def_id() {
@@ -235,12 +243,17 @@ impl ImportMap {
                 ItemInNs::Values(module_def_id)
             };
 
-            let attrs = &db.attrs(item.into());
+            let attr_id = item.into();
+            let attrs = &db.attrs(attr_id);
+            let item_do_not_complete = Complete::extract(false, attrs);
+            let do_not_complete =
+                Complete::for_trait_item(trait_import_info.complete, item_do_not_complete);
             let assoc_item_info = ImportInfo {
                 container: trait_import_info.container,
                 name: assoc_item_name.clone(),
                 is_doc_hidden: attrs.has_doc_hidden(),
                 is_unstable: attrs.is_unstable(),
+                complete: do_not_complete,
             };
 
             let (infos, _) =
@@ -398,7 +411,7 @@ pub fn search_dependencies(
     db: &dyn DefDatabase,
     krate: Crate,
     query: &Query,
-) -> FxHashSet<ItemInNs> {
+) -> FxHashSet<(ItemInNs, Complete)> {
     let _p = tracing::info_span!("search_dependencies", ?query).entered();
 
     let import_maps: Vec<_> =
@@ -439,7 +452,7 @@ fn search_maps(
     import_maps: &[Arc<ImportMap>],
     mut stream: fst::map::Union<'_>,
     query: &Query,
-) -> FxHashSet<ItemInNs> {
+) -> FxHashSet<(ItemInNs, Complete)> {
     let mut res = FxHashSet::default();
     while let Some((_, indexed_values)) = stream.next() {
         for &IndexedValue { index: import_map_idx, value } in indexed_values {
@@ -459,8 +472,9 @@ fn search_maps(
                 })
                 .filter(|&(_, info)| {
                     query.search_mode.check(&query.query, query.case_sensitive, info.name.as_str())
-                });
-            res.extend(iter.map(TupleExt::head));
+                })
+                .map(|(item, import_info)| (item, import_info.complete));
+            res.extend(iter);
         }
     }
 
@@ -521,7 +535,7 @@ mod tests {
 
         let actual = search_dependencies(db.upcast(), krate, &query)
             .into_iter()
-            .filter_map(|dependency| {
+            .filter_map(|(dependency, _)| {
                 let dependency_krate = dependency.krate(db.upcast())?;
                 let dependency_imports = db.import_map(dependency_krate);
 
diff --git a/crates/hir-def/src/item_scope.rs b/crates/hir-def/src/item_scope.rs
index 47ad020043ce..a299cfbdd19a 100644
--- a/crates/hir-def/src/item_scope.rs
+++ b/crates/hir-def/src/item_scope.rs
@@ -358,7 +358,7 @@ impl ItemScope {
     }
 
     /// Get a name from current module scope, legacy macros are not included
-    pub(crate) fn get(&self, name: &Name) -> PerNs {
+    pub fn get(&self, name: &Name) -> PerNs {
         PerNs {
             types: self.types.get(name).copied(),
             values: self.values.get(name).copied(),
diff --git a/crates/hir-def/src/item_tree.rs b/crates/hir-def/src/item_tree.rs
index 80d666a0aba1..1cabb665d062 100644
--- a/crates/hir-def/src/item_tree.rs
+++ b/crates/hir-def/src/item_tree.rs
@@ -962,7 +962,7 @@ pub struct Param {
 
 bitflags::bitflags! {
     #[derive(Debug, Clone, Copy, Eq, PartialEq, Default)]
-    pub(crate) struct FnFlags: u16 {
+    pub struct FnFlags: u16 {
         const HAS_SELF_PARAM = 1 << 0;
         const HAS_BODY = 1 << 1;
         const HAS_DEFAULT_KW = 1 << 2;
@@ -977,6 +977,7 @@ bitflags::bitflags! {
         /// it if needed.
         const HAS_TARGET_FEATURE = 1 << 8;
         const DEPRECATED_SAFE_2024 = 1 << 9;
+        const RUSTC_ALLOW_INCOHERENT_IMPL = 1 << 10;
     }
 }
 
@@ -1050,15 +1051,22 @@ pub struct Const {
 pub struct Static {
     pub name: Name,
     pub visibility: RawVisibilityId,
-    // TODO: use bitflags when we have more flags
-    pub mutable: bool,
-    pub has_safe_kw: bool,
-    pub has_unsafe_kw: bool,
+    pub flags: StaticFlags,
     pub type_ref: TypeRefId,
     pub ast_id: FileAstId<ast::Static>,
     pub types_map: Arc<TypesMap>,
 }
 
+bitflags::bitflags! {
+    #[derive(Debug, Clone, Copy, PartialEq, Eq)]
+    pub struct StaticFlags: u8 {
+        const MUTABLE = 1 << 0;
+        const IS_EXTERN = 1 << 1;
+        const HAS_SAFE_KW = 1 << 2;
+        const HAS_UNSAFE_KW = 1 << 3;
+    }
+}
+
 #[derive(Debug, Clone, Eq, PartialEq)]
 pub struct Trait {
     pub name: Name,
diff --git a/crates/hir-def/src/item_tree/lower.rs b/crates/hir-def/src/item_tree/lower.rs
index 5389ba49c577..4a3deec5ffd2 100644
--- a/crates/hir-def/src/item_tree/lower.rs
+++ b/crates/hir-def/src/item_tree/lower.rs
@@ -29,8 +29,8 @@ use crate::{
         GenericModItem, Idx, Impl, ImportAlias, Interned, ItemTree, ItemTreeData,
         ItemTreeSourceMaps, ItemTreeSourceMapsBuilder, Macro2, MacroCall, MacroRules, Mod, ModItem,
         ModKind, ModPath, Mutability, Name, Param, Path, Range, RawAttrs, RawIdx, RawVisibilityId,
-        Static, Struct, StructKind, Trait, TraitAlias, TypeAlias, Union, Use, UseTree, UseTreeKind,
-        Variant,
+        Static, StaticFlags, Struct, StructKind, Trait, TraitAlias, TypeAlias, Union, Use, UseTree,
+        UseTreeKind, Variant,
     },
     lower::LowerCtx,
     path::AssociatedTypeBinding,
@@ -620,22 +620,23 @@ impl<'a> Ctx<'a> {
         let name = static_.name()?.as_name();
         let type_ref = TypeRef::from_ast_opt(&mut body_ctx, static_.ty());
         let visibility = self.lower_visibility(static_);
-        let mutable = static_.mut_token().is_some();
-        let has_safe_kw = static_.safe_token().is_some();
-        let has_unsafe_kw = static_.unsafe_token().is_some();
+
+        let mut flags = StaticFlags::empty();
+        if static_.mut_token().is_some() {
+            flags |= StaticFlags::MUTABLE;
+        }
+        if static_.safe_token().is_some() {
+            flags |= StaticFlags::HAS_SAFE_KW;
+        }
+        if static_.unsafe_token().is_some() {
+            flags |= StaticFlags::HAS_UNSAFE_KW;
+        }
+
         let ast_id = self.source_ast_id_map.ast_id(static_);
         types_map.shrink_to_fit();
         types_source_map.shrink_to_fit();
-        let res = Static {
-            name,
-            visibility,
-            mutable,
-            type_ref,
-            ast_id,
-            has_safe_kw,
-            has_unsafe_kw,
-            types_map: Arc::new(types_map),
-        };
+        let res =
+            Static { name, visibility, type_ref, ast_id, flags, types_map: Arc::new(types_map) };
         self.source_maps.statics.push(types_source_map);
         Some(id(self.data().statics.alloc(res)))
     }
diff --git a/crates/hir-def/src/item_tree/pretty.rs b/crates/hir-def/src/item_tree/pretty.rs
index f44a000ac1ba..b79b8a28b3ed 100644
--- a/crates/hir-def/src/item_tree/pretty.rs
+++ b/crates/hir-def/src/item_tree/pretty.rs
@@ -11,8 +11,8 @@ use crate::{
         AttrOwner, Const, DefDatabase, Enum, ExternBlock, ExternCrate, Field, FieldParent,
         FieldsShape, FileItemTreeId, FnFlags, Function, GenericModItem, GenericParams, Impl,
         ItemTree, Macro2, MacroCall, MacroRules, Mod, ModItem, ModKind, Param, Path, RawAttrs,
-        RawVisibilityId, Static, Struct, Trait, TraitAlias, TypeAlias, TypeBound, Union, Use,
-        UseTree, UseTreeKind, Variant,
+        RawVisibilityId, Static, StaticFlags, Struct, Trait, TraitAlias, TypeAlias, TypeBound,
+        Union, Use, UseTree, UseTreeKind, Variant,
     },
     pretty::{print_path, print_type_bounds, print_type_ref},
     type_ref::{TypeRefId, TypesMap},
@@ -418,26 +418,18 @@ impl Printer<'_> {
                 wln!(self, " = _;");
             }
             ModItem::Static(it) => {
-                let Static {
-                    name,
-                    visibility,
-                    mutable,
-                    type_ref,
-                    ast_id,
-                    has_safe_kw,
-                    has_unsafe_kw,
-                    types_map,
-                } = &self.tree[it];
+                let Static { name, visibility, type_ref, ast_id, types_map, flags } =
+                    &self.tree[it];
                 self.print_ast_id(ast_id.erase());
                 self.print_visibility(*visibility);
-                if *has_safe_kw {
+                if flags.contains(StaticFlags::HAS_SAFE_KW) {
                     w!(self, "safe ");
                 }
-                if *has_unsafe_kw {
+                if flags.contains(StaticFlags::HAS_UNSAFE_KW) {
                     w!(self, "unsafe ");
                 }
                 w!(self, "static ");
-                if *mutable {
+                if flags.contains(StaticFlags::MUTABLE) {
                     w!(self, "mut ");
                 }
                 w!(self, "{}: ", name.display(self.db.upcast(), self.edition));
diff --git a/crates/hir-def/src/lib.rs b/crates/hir-def/src/lib.rs
index 4601960c7efb..ab897f4e3619 100644
--- a/crates/hir-def/src/lib.rs
+++ b/crates/hir-def/src/lib.rs
@@ -53,7 +53,7 @@ pub mod find_path;
 pub mod import_map;
 pub mod visibility;
 
-use intern::Interned;
+use intern::{Interned, sym};
 pub use rustc_abi as layout;
 use triomphe::Arc;
 
@@ -86,6 +86,7 @@ use syntax::{AstNode, ast};
 pub use hir_expand::{Intern, Lookup, tt};
 
 use crate::{
+    attr::Attrs,
     builtin_type::BuiltinType,
     data::adt::VariantData,
     db::DefDatabase,
@@ -1502,3 +1503,81 @@ pub struct UnresolvedMacro {
 
 #[derive(Default, Debug, Eq, PartialEq, Clone, Copy)]
 pub struct SyntheticSyntax;
+
+// Feature: Completions Attribute
+// Crate authors can opt their type out of completions in some cases.
+// This is done with the `#[rust_analyzer::completions(...)]` attribute.
+//
+// All completeable things support `#[rust_analyzer::completions(ignore_flyimport)]`,
+// which causes the thing to get excluded from flyimport completion. It will still
+// be completed when in scope. This is analogous to the setting `rust-analyzer.completion.autoimport.exclude`
+// with `"type": "always"`.
+//
+// In addition, traits support two more modes: `#[rust_analyzer::completions(ignore_flyimport_methods)]`,
+// which means the trait itself may still be flyimported but its methods won't, and
+// `#[rust_analyzer::completions(ignore_methods)]`, which means the methods won't be completed even when
+// the trait is in scope (but the trait itself may still be completed). The methods will still be completed
+// on `dyn Trait`, `impl Trait` or where the trait is specified in bounds. These modes correspond to
+// the settings `rust-analyzer.completion.autoimport.exclude` with `"type": "methods"` and
+// `rust-analyzer.completion.excludeTraits`, respectively.
+//
+// Malformed attributes will be ignored without warnings.
+//
+// Note that users have no way to override this attribute, so be careful and only include things
+// users definitely do not want to be completed!
+
+/// `#[rust_analyzer::completions(...)]` options.
+#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
+pub enum Complete {
+    /// No `#[rust_analyzer::completions(...)]`.
+    Yes,
+    /// `#[rust_analyzer::completions(ignore_flyimport)]`.
+    IgnoreFlyimport,
+    /// `#[rust_analyzer::completions(ignore_flyimport_methods)]` (on a trait only).
+    IgnoreFlyimportMethods,
+    /// `#[rust_analyzer::completions(ignore_methods)]` (on a trait only).
+    IgnoreMethods,
+}
+
+impl Complete {
+    pub fn extract(is_trait: bool, attrs: &Attrs) -> Complete {
+        let mut do_not_complete = Complete::Yes;
+        for ra_attr in attrs.rust_analyzer_tool() {
+            let segments = ra_attr.path.segments();
+            if segments.len() != 2 {
+                continue;
+            }
+            let action = segments[1].symbol();
+            if *action == sym::completions {
+                match ra_attr.token_tree_value().map(|tt| tt.token_trees().flat_tokens()) {
+                    Some([tt::TokenTree::Leaf(tt::Leaf::Ident(ident))]) => {
+                        if ident.sym == sym::ignore_flyimport {
+                            do_not_complete = Complete::IgnoreFlyimport;
+                        } else if is_trait {
+                            if ident.sym == sym::ignore_methods {
+                                do_not_complete = Complete::IgnoreMethods;
+                            } else if ident.sym == sym::ignore_flyimport_methods {
+                                do_not_complete = Complete::IgnoreFlyimportMethods;
+                            }
+                        }
+                    }
+                    _ => {}
+                }
+            }
+        }
+        do_not_complete
+    }
+
+    #[inline]
+    pub fn for_trait_item(trait_attr: Complete, item_attr: Complete) -> Complete {
+        match (trait_attr, item_attr) {
+            (
+                Complete::IgnoreFlyimportMethods
+                | Complete::IgnoreFlyimport
+                | Complete::IgnoreMethods,
+                _,
+            ) => Complete::IgnoreFlyimport,
+            _ => item_attr,
+        }
+    }
+}
diff --git a/crates/hir-ty/src/diagnostics/decl_check.rs b/crates/hir-ty/src/diagnostics/decl_check.rs
index ce0ffb179264..5fb8e8e60a15 100644
--- a/crates/hir-ty/src/diagnostics/decl_check.rs
+++ b/crates/hir-ty/src/diagnostics/decl_check.rs
@@ -558,7 +558,7 @@ impl<'a> DeclValidator<'a> {
 
     fn validate_static(&mut self, static_id: StaticId) {
         let data = self.db.static_data(static_id);
-        if data.is_extern {
+        if data.is_extern() {
             cov_mark::hit!(extern_static_incorrect_case_ignored);
             return;
         }
diff --git a/crates/hir-ty/src/diagnostics/unsafe_check.rs b/crates/hir-ty/src/diagnostics/unsafe_check.rs
index 562a9aa085a1..b4fe41714531 100644
--- a/crates/hir-ty/src/diagnostics/unsafe_check.rs
+++ b/crates/hir-ty/src/diagnostics/unsafe_check.rs
@@ -362,9 +362,9 @@ impl<'a> UnsafeVisitor<'a> {
             self.resolver.resolve_path_in_value_ns(self.db.upcast(), path, hygiene);
         if let Some(ResolveValueResult::ValueNs(ValueNs::StaticId(id), _)) = value_or_partial {
             let static_data = self.db.static_data(id);
-            if static_data.mutable {
+            if static_data.mutable() {
                 self.on_unsafe_op(node, UnsafetyReason::MutableStatic);
-            } else if static_data.is_extern && !static_data.has_safe_kw {
+            } else if static_data.is_extern() && !static_data.has_safe_kw() {
                 self.on_unsafe_op(node, UnsafetyReason::ExternStatic);
             }
         }
diff --git a/crates/hir-ty/src/lower.rs b/crates/hir-ty/src/lower.rs
index 9df8d9cebbe8..355a4031e9f2 100644
--- a/crates/hir-ty/src/lower.rs
+++ b/crates/hir-ty/src/lower.rs
@@ -1577,7 +1577,7 @@ pub(crate) fn type_for_type_alias_with_diagnostics_query(
     let mut ctx = TyLoweringContext::new(db, &resolver, &type_alias_data.types_map, t.into())
         .with_impl_trait_mode(ImplTraitLoweringMode::Opaque)
         .with_type_param_mode(ParamLoweringMode::Variable);
-    let inner = if type_alias_data.is_extern {
+    let inner = if type_alias_data.is_extern() {
         TyKind::Foreign(crate::to_foreign_def_id(t)).intern(Interner)
     } else {
         type_alias_data
diff --git a/crates/hir-ty/src/method_resolution.rs b/crates/hir-ty/src/method_resolution.rs
index d887013b6ff3..895cc58147b1 100644
--- a/crates/hir-ty/src/method_resolution.rs
+++ b/crates/hir-ty/src/method_resolution.rs
@@ -401,7 +401,7 @@ pub fn def_crates(db: &dyn HirDatabase, ty: &Ty, cur_crate: Crate) -> Option<Sma
         }
         &TyKind::Foreign(id) => {
             let alias = from_foreign_def_id(id);
-            Some(if db.type_alias_data(alias).rustc_has_incoherent_inherent_impls {
+            Some(if db.type_alias_data(alias).rustc_has_incoherent_inherent_impls() {
                 db.incoherent_inherent_impl_crates(cur_crate, TyFingerprint::ForeignType(id))
             } else {
                 smallvec![alias.module(db.upcast()).krate()]
@@ -843,9 +843,11 @@ fn is_inherent_impl_coherent(
         rustc_has_incoherent_inherent_impls
             && !items.items.is_empty()
             && items.items.iter().all(|&(_, assoc)| match assoc {
-                AssocItemId::FunctionId(it) => db.function_data(it).rustc_allow_incoherent_impl,
-                AssocItemId::ConstId(it) => db.const_data(it).rustc_allow_incoherent_impl,
-                AssocItemId::TypeAliasId(it) => db.type_alias_data(it).rustc_allow_incoherent_impl,
+                AssocItemId::FunctionId(it) => db.function_data(it).rustc_allow_incoherent_impl(),
+                AssocItemId::ConstId(it) => db.const_data(it).rustc_allow_incoherent_impl(),
+                AssocItemId::TypeAliasId(it) => {
+                    db.type_alias_data(it).rustc_allow_incoherent_impl()
+                }
             })
     }
 }
diff --git a/crates/hir-ty/src/mir/eval.rs b/crates/hir-ty/src/mir/eval.rs
index ee412dd00b3a..498d707bdafe 100644
--- a/crates/hir-ty/src/mir/eval.rs
+++ b/crates/hir-ty/src/mir/eval.rs
@@ -2756,7 +2756,7 @@ impl Evaluator<'_> {
             return Ok(*o);
         };
         let static_data = self.db.static_data(st);
-        let result = if !static_data.is_extern {
+        let result = if !static_data.is_extern() {
             let konst = self.db.const_eval_static(st).map_err(|e| {
                 MirEvalError::ConstEvalError(static_data.name.as_str().to_owned(), Box::new(e))
             })?;
diff --git a/crates/hir/src/display.rs b/crates/hir/src/display.rs
index ec34fd80ad68..673c336cc364 100644
--- a/crates/hir/src/display.rs
+++ b/crates/hir/src/display.rs
@@ -739,7 +739,7 @@ impl HirDisplay for Static {
         write_visibility(self.module(f.db).id, self.visibility(f.db), f)?;
         let data = f.db.static_data(self.id);
         f.write_str("static ")?;
-        if data.mutable {
+        if data.mutable() {
             f.write_str("mut ")?;
         }
         write!(f, "{}: ", data.name.display(f.db.upcast(), f.edition()))?;
diff --git a/crates/hir/src/lib.rs b/crates/hir/src/lib.rs
index 124cbd2ed1a3..c5ed0446839f 100644
--- a/crates/hir/src/lib.rs
+++ b/crates/hir/src/lib.rs
@@ -114,6 +114,7 @@ pub use crate::{
 pub use {
     cfg::{CfgAtom, CfgExpr, CfgOptions},
     hir_def::{
+        Complete,
         ImportPathConfig,
         attr::{AttrSourceMap, Attrs, AttrsWithOwner},
         data::adt::StructKind,
@@ -254,14 +255,17 @@ impl Crate {
         self,
         db: &dyn DefDatabase,
         query: import_map::Query,
-    ) -> impl Iterator<Item = Either<ModuleDef, Macro>> {
+    ) -> impl Iterator<Item = (Either<ModuleDef, Macro>, Complete)> {
         let _p = tracing::info_span!("query_external_importables").entered();
-        import_map::search_dependencies(db, self.into(), &query).into_iter().map(|item| {
-            match ItemInNs::from(item) {
-                ItemInNs::Types(mod_id) | ItemInNs::Values(mod_id) => Either::Left(mod_id),
-                ItemInNs::Macros(mac_id) => Either::Right(mac_id),
-            }
-        })
+        import_map::search_dependencies(db, self.into(), &query).into_iter().map(
+            |(item, do_not_complete)| {
+                let item = match ItemInNs::from(item) {
+                    ItemInNs::Types(mod_id) | ItemInNs::Values(mod_id) => Either::Left(mod_id),
+                    ItemInNs::Macros(mac_id) => Either::Right(mac_id),
+                };
+                (item, do_not_complete)
+            },
+        )
     }
 
     pub fn all(db: &dyn HirDatabase) -> Vec<Crate> {
@@ -811,7 +815,7 @@ impl Module {
                 let items = &db.trait_items(trait_.into()).items;
                 let required_items = items.iter().filter(|&(_, assoc)| match *assoc {
                     AssocItemId::FunctionId(it) => !db.function_data(it).has_body(),
-                    AssocItemId::ConstId(id) => !db.const_data(id).has_body,
+                    AssocItemId::ConstId(id) => !db.const_data(id).has_body(),
                     AssocItemId::TypeAliasId(it) => db.type_alias_data(it).type_ref.is_none(),
                 });
                 impl_assoc_items_scratch.extend(db.impl_items(impl_def.id).items.iter().cloned());
@@ -2812,7 +2816,7 @@ impl Static {
     }
 
     pub fn is_mut(self, db: &dyn HirDatabase) -> bool {
-        db.static_data(self.id).mutable
+        db.static_data(self.id).mutable()
     }
 
     pub fn value(self, db: &dyn HirDatabase) -> Option<ast::Expr> {
@@ -2932,6 +2936,11 @@ impl Trait {
             .map(|it| it.as_ref().clone().into_boxed_slice())
             .unwrap_or_default()
     }
+
+    /// `#[rust_analyzer::completions(...)]` mode.
+    pub fn complete(self, db: &dyn HirDatabase) -> Complete {
+        Complete::extract(true, &self.attrs(db))
+    }
 }
 
 impl HasVisibility for Trait {
@@ -6359,3 +6368,48 @@ where
         self(item)
     }
 }
+
+pub fn resolve_absolute_path<'a, I: Iterator<Item = Symbol> + Clone + 'a>(
+    db: &'a dyn HirDatabase,
+    mut segments: I,
+) -> impl Iterator<Item = ItemInNs> + use<'a, I> {
+    segments
+        .next()
+        .into_iter()
+        .flat_map(move |crate_name| {
+            db.all_crates()
+                .iter()
+                .filter(|&krate| {
+                    krate
+                        .extra_data(db)
+                        .display_name
+                        .as_ref()
+                        .is_some_and(|name| *name.crate_name().symbol() == crate_name)
+                })
+                .filter_map(|&krate| {
+                    let segments = segments.clone();
+                    let mut def_map = db.crate_def_map(krate);
+                    let mut module = &def_map[DefMap::ROOT];
+                    let mut segments = segments.with_position().peekable();
+                    while let Some((_, segment)) = segments.next_if(|&(position, _)| {
+                        !matches!(position, itertools::Position::Last | itertools::Position::Only)
+                    }) {
+                        let res = module
+                            .scope
+                            .get(&Name::new_symbol_root(segment))
+                            .take_types()
+                            .and_then(|res| match res {
+                                ModuleDefId::ModuleId(it) => Some(it),
+                                _ => None,
+                            })?;
+                        def_map = res.def_map(db.upcast());
+                        module = &def_map[res.local_id];
+                    }
+                    let (_, item_name) = segments.next()?;
+                    let res = module.scope.get(&Name::new_symbol_root(item_name));
+                    Some(res.iter_items().map(|(item, _)| item.into()))
+                })
+                .collect::<Vec<_>>()
+        })
+        .flatten()
+}
diff --git a/crates/hir/src/symbols.rs b/crates/hir/src/symbols.rs
index ae70f6f0ecc0..679f775e1bd9 100644
--- a/crates/hir/src/symbols.rs
+++ b/crates/hir/src/symbols.rs
@@ -2,7 +2,7 @@
 
 use either::Either;
 use hir_def::{
-    AdtId, AssocItemId, DefWithBodyId, ExternCrateId, HasModule, ImplId, Lookup, MacroId,
+    AdtId, AssocItemId, Complete, DefWithBodyId, ExternCrateId, HasModule, ImplId, Lookup, MacroId,
     ModuleDefId, ModuleId, TraitId,
     db::DefDatabase,
     item_scope::{ImportId, ImportOrExternCrate, ImportOrGlob},
@@ -34,6 +34,7 @@ pub struct FileSymbol {
     /// Whether this symbol is a doc alias for the original symbol.
     pub is_alias: bool,
     pub is_assoc: bool,
+    pub do_not_complete: Complete,
 }
 
 #[derive(Debug, Clone, PartialEq, Eq, Hash)]
@@ -122,35 +123,43 @@ impl<'a> SymbolCollector<'a> {
             match def {
                 ModuleDefId::ModuleId(id) => this.push_module(id, name),
                 ModuleDefId::FunctionId(id) => {
-                    this.push_decl(id, name, false);
+                    this.push_decl(id, name, false, None);
                     this.collect_from_body(id, Some(name.clone()));
                 }
-                ModuleDefId::AdtId(AdtId::StructId(id)) => this.push_decl(id, name, false),
-                ModuleDefId::AdtId(AdtId::EnumId(id)) => this.push_decl(id, name, false),
-                ModuleDefId::AdtId(AdtId::UnionId(id)) => this.push_decl(id, name, false),
+                ModuleDefId::AdtId(AdtId::StructId(id)) => {
+                    this.push_decl(id, name, false, None);
+                }
+                ModuleDefId::AdtId(AdtId::EnumId(id)) => {
+                    this.push_decl(id, name, false, None);
+                }
+                ModuleDefId::AdtId(AdtId::UnionId(id)) => {
+                    this.push_decl(id, name, false, None);
+                }
                 ModuleDefId::ConstId(id) => {
-                    this.push_decl(id, name, false);
+                    this.push_decl(id, name, false, None);
                     this.collect_from_body(id, Some(name.clone()));
                 }
                 ModuleDefId::StaticId(id) => {
-                    this.push_decl(id, name, false);
+                    this.push_decl(id, name, false, None);
                     this.collect_from_body(id, Some(name.clone()));
                 }
                 ModuleDefId::TraitId(id) => {
-                    this.push_decl(id, name, false);
-                    this.collect_from_trait(id);
+                    let trait_do_not_complete = this.push_decl(id, name, false, None);
+                    this.collect_from_trait(id, trait_do_not_complete);
                 }
                 ModuleDefId::TraitAliasId(id) => {
-                    this.push_decl(id, name, false);
+                    this.push_decl(id, name, false, None);
                 }
                 ModuleDefId::TypeAliasId(id) => {
-                    this.push_decl(id, name, false);
+                    this.push_decl(id, name, false, None);
+                }
+                ModuleDefId::MacroId(id) => {
+                    match id {
+                        MacroId::Macro2Id(id) => this.push_decl(id, name, false, None),
+                        MacroId::MacroRulesId(id) => this.push_decl(id, name, false, None),
+                        MacroId::ProcMacroId(id) => this.push_decl(id, name, false, None),
+                    };
                 }
-                ModuleDefId::MacroId(id) => match id {
-                    MacroId::Macro2Id(id) => this.push_decl(id, name, false),
-                    MacroId::MacroRulesId(id) => this.push_decl(id, name, false),
-                    MacroId::ProcMacroId(id) => this.push_decl(id, name, false),
-                },
                 // Don't index these.
                 ModuleDefId::BuiltinType(_) => {}
                 ModuleDefId::EnumVariantId(_) => {}
@@ -194,6 +203,7 @@ impl<'a> SymbolCollector<'a> {
                 loc: dec_loc,
                 is_alias: false,
                 is_assoc: false,
+                do_not_complete: Complete::Yes,
             });
         };
 
@@ -223,6 +233,7 @@ impl<'a> SymbolCollector<'a> {
                     loc: dec_loc,
                     is_alias: false,
                     is_assoc: false,
+                    do_not_complete: Complete::Yes,
                 });
             };
 
@@ -281,10 +292,10 @@ impl<'a> SymbolCollector<'a> {
             for &id in id {
                 if id.module(self.db.upcast()) == module_id {
                     match id {
-                        MacroId::Macro2Id(id) => self.push_decl(id, name, false),
-                        MacroId::MacroRulesId(id) => self.push_decl(id, name, false),
-                        MacroId::ProcMacroId(id) => self.push_decl(id, name, false),
-                    }
+                        MacroId::Macro2Id(id) => self.push_decl(id, name, false, None),
+                        MacroId::MacroRulesId(id) => self.push_decl(id, name, false, None),
+                        MacroId::ProcMacroId(id) => self.push_decl(id, name, false, None),
+                    };
                 }
             }
         }
@@ -314,16 +325,16 @@ impl<'a> SymbolCollector<'a> {
         );
         self.with_container_name(impl_name, |s| {
             for &(ref name, assoc_item_id) in &self.db.impl_items(impl_id).items {
-                s.push_assoc_item(assoc_item_id, name)
+                s.push_assoc_item(assoc_item_id, name, None)
             }
         })
     }
 
-    fn collect_from_trait(&mut self, trait_id: TraitId) {
+    fn collect_from_trait(&mut self, trait_id: TraitId, trait_do_not_complete: Complete) {
         let trait_data = self.db.trait_data(trait_id);
         self.with_container_name(Some(trait_data.name.as_str().into()), |s| {
             for &(ref name, assoc_item_id) in &self.db.trait_items(trait_id).items {
-                s.push_assoc_item(assoc_item_id, name);
+                s.push_assoc_item(assoc_item_id, name, Some(trait_do_not_complete));
             }
         });
     }
@@ -338,15 +349,26 @@ impl<'a> SymbolCollector<'a> {
         }
     }
 
-    fn push_assoc_item(&mut self, assoc_item_id: AssocItemId, name: &Name) {
+    fn push_assoc_item(
+        &mut self,
+        assoc_item_id: AssocItemId,
+        name: &Name,
+        trait_do_not_complete: Option<Complete>,
+    ) {
         match assoc_item_id {
-            AssocItemId::FunctionId(id) => self.push_decl(id, name, true),
-            AssocItemId::ConstId(id) => self.push_decl(id, name, true),
-            AssocItemId::TypeAliasId(id) => self.push_decl(id, name, true),
-        }
+            AssocItemId::FunctionId(id) => self.push_decl(id, name, true, trait_do_not_complete),
+            AssocItemId::ConstId(id) => self.push_decl(id, name, true, trait_do_not_complete),
+            AssocItemId::TypeAliasId(id) => self.push_decl(id, name, true, trait_do_not_complete),
+        };
     }
 
-    fn push_decl<L>(&mut self, id: L, name: &Name, is_assoc: bool)
+    fn push_decl<L>(
+        &mut self,
+        id: L,
+        name: &Name,
+        is_assoc: bool,
+        trait_do_not_complete: Option<Complete>,
+    ) -> Complete
     where
         L: Lookup<Database = dyn DefDatabase> + Into<ModuleDefId>,
         <L as Lookup>::Data: HasSource,
@@ -354,7 +376,7 @@ impl<'a> SymbolCollector<'a> {
     {
         let loc = id.lookup(self.db.upcast());
         let source = loc.source(self.db.upcast());
-        let Some(name_node) = source.value.name() else { return };
+        let Some(name_node) = source.value.name() else { return Complete::Yes };
         let def = ModuleDef::from(id.into());
         let dec_loc = DeclarationLocation {
             hir_file_id: source.file_id,
@@ -362,7 +384,14 @@ impl<'a> SymbolCollector<'a> {
             name_ptr: AstPtr::new(&name_node).wrap_left(),
         };
 
+        let mut do_not_complete = Complete::Yes;
+
         if let Some(attrs) = def.attrs(self.db) {
+            do_not_complete = Complete::extract(matches!(def, ModuleDef::Trait(_)), &attrs);
+            if let Some(trait_do_not_complete) = trait_do_not_complete {
+                do_not_complete = Complete::for_trait_item(trait_do_not_complete, do_not_complete);
+            }
+
             for alias in attrs.doc_aliases() {
                 self.symbols.insert(FileSymbol {
                     name: alias.clone(),
@@ -371,6 +400,7 @@ impl<'a> SymbolCollector<'a> {
                     container_name: self.current_container_name.clone(),
                     is_alias: true,
                     is_assoc,
+                    do_not_complete,
                 });
             }
         }
@@ -382,7 +412,10 @@ impl<'a> SymbolCollector<'a> {
             loc: dec_loc,
             is_alias: false,
             is_assoc,
+            do_not_complete,
         });
+
+        do_not_complete
     }
 
     fn push_module(&mut self, module_id: ModuleId, name: &Name) {
@@ -399,7 +432,10 @@ impl<'a> SymbolCollector<'a> {
 
         let def = ModuleDef::Module(module_id.into());
 
+        let mut do_not_complete = Complete::Yes;
         if let Some(attrs) = def.attrs(self.db) {
+            do_not_complete = Complete::extract(matches!(def, ModuleDef::Trait(_)), &attrs);
+
             for alias in attrs.doc_aliases() {
                 self.symbols.insert(FileSymbol {
                     name: alias.clone(),
@@ -408,6 +444,7 @@ impl<'a> SymbolCollector<'a> {
                     container_name: self.current_container_name.clone(),
                     is_alias: true,
                     is_assoc: false,
+                    do_not_complete,
                 });
             }
         }
@@ -419,6 +456,7 @@ impl<'a> SymbolCollector<'a> {
             loc: dec_loc,
             is_alias: false,
             is_assoc: false,
+            do_not_complete,
         });
     }
 }
diff --git a/crates/ide-assists/src/handlers/replace_derive_with_manual_impl.rs b/crates/ide-assists/src/handlers/replace_derive_with_manual_impl.rs
index 691ae931f8ad..feeea8872a44 100644
--- a/crates/ide-assists/src/handlers/replace_derive_with_manual_impl.rs
+++ b/crates/ide-assists/src/handlers/replace_derive_with_manual_impl.rs
@@ -78,7 +78,7 @@ pub(crate) fn replace_derive_with_manual_impl(
         NameToImport::exact_case_sensitive(path.segments().last()?.to_string()),
         items_locator::AssocSearchMode::Exclude,
     )
-    .filter_map(|item| match item.into_module_def() {
+    .filter_map(|(item, _)| match item.into_module_def() {
         ModuleDef::Trait(trait_) => Some(trait_),
         _ => None,
     })
diff --git a/crates/ide-completion/src/completions/dot.rs b/crates/ide-completion/src/completions/dot.rs
index dea983b6ea81..4f21136d214e 100644
--- a/crates/ide-completion/src/completions/dot.rs
+++ b/crates/ide-completion/src/completions/dot.rs
@@ -2,7 +2,7 @@
 
 use std::ops::ControlFlow;
 
-use hir::{HasContainer, ItemContainer, MethodCandidateCallback, Name};
+use hir::{Complete, HasContainer, ItemContainer, MethodCandidateCallback, Name};
 use ide_db::FxHashSet;
 use syntax::SmolStr;
 
@@ -259,7 +259,9 @@ fn complete_methods(
             // This needs to come before the `seen_methods` test, so that if we see the same method twice,
             // once as inherent and once not, we will include it.
             if let ItemContainer::Trait(trait_) = func.container(self.ctx.db) {
-                if self.ctx.exclude_traits.contains(&trait_) {
+                if self.ctx.exclude_traits.contains(&trait_)
+                    || trait_.complete(self.ctx.db) == Complete::IgnoreMethods
+                {
                     return ControlFlow::Continue(());
                 }
             }
diff --git a/crates/ide-completion/src/completions/expr.rs b/crates/ide-completion/src/completions/expr.rs
index 7219a5f1b011..0494d4223d18 100644
--- a/crates/ide-completion/src/completions/expr.rs
+++ b/crates/ide-completion/src/completions/expr.rs
@@ -2,7 +2,7 @@
 
 use std::ops::ControlFlow;
 
-use hir::{Name, PathCandidateCallback, ScopeDef, sym};
+use hir::{Complete, Name, PathCandidateCallback, ScopeDef, sym};
 use ide_db::FxHashSet;
 use syntax::ast;
 
@@ -33,10 +33,10 @@ where
     fn on_trait_item(&mut self, item: hir::AssocItem) -> ControlFlow<()> {
         // The excluded check needs to come before the `seen` test, so that if we see the same method twice,
         // once as inherent and once not, we will include it.
-        if item
-            .container_trait(self.ctx.db)
-            .is_none_or(|trait_| !self.ctx.exclude_traits.contains(&trait_))
-            && self.seen.insert(item)
+        if item.container_trait(self.ctx.db).is_none_or(|trait_| {
+            !self.ctx.exclude_traits.contains(&trait_)
+                && trait_.complete(self.ctx.db) != Complete::IgnoreMethods
+        }) && self.seen.insert(item)
         {
             (self.add_assoc_item)(self.acc, item);
         }
@@ -104,7 +104,9 @@ pub(crate) fn complete_expr_path(
             .iter()
             .copied()
             .map(hir::Trait::from)
-            .filter(|it| !ctx.exclude_traits.contains(it))
+            .filter(|it| {
+                !ctx.exclude_traits.contains(it) && it.complete(ctx.db) != Complete::IgnoreMethods
+            })
             .flat_map(|it| it.items(ctx.sema.db))
             .for_each(|item| add_assoc_item(acc, item)),
         Qualified::TypeAnchor { trait_: Some(trait_), .. } => {
diff --git a/crates/ide-completion/src/completions/flyimport.rs b/crates/ide-completion/src/completions/flyimport.rs
index b3ba07648930..a74756138090 100644
--- a/crates/ide-completion/src/completions/flyimport.rs
+++ b/crates/ide-completion/src/completions/flyimport.rs
@@ -268,19 +268,7 @@ fn import_on_the_fly(
                 && !ctx.is_item_hidden(original_item)
                 && ctx.check_stability(original_item.attrs(ctx.db).as_deref())
         })
-        .filter(|import| {
-            let def = import.item_to_import.into_module_def();
-            if let Some(&kind) = ctx.exclude_flyimport.get(&def) {
-                if kind == AutoImportExclusionType::Always {
-                    return false;
-                }
-                let method_imported = import.item_to_import != import.original_item;
-                if method_imported {
-                    return false;
-                }
-            }
-            true
-        })
+        .filter(|import| filter_excluded_flyimport(ctx, import))
         .sorted_by(|a, b| {
             let key = |import_path| {
                 (
@@ -366,24 +354,7 @@ fn import_on_the_fly_method(
             !ctx.is_item_hidden(&import.item_to_import)
                 && !ctx.is_item_hidden(&import.original_item)
         })
-        .filter(|import| {
-            let def = import.item_to_import.into_module_def();
-            if let Some(&kind) = ctx.exclude_flyimport.get(&def) {
-                if kind == AutoImportExclusionType::Always {
-                    return false;
-                }
-                let method_imported = import.item_to_import != import.original_item;
-                if method_imported {
-                    return false;
-                }
-            }
-
-            if let ModuleDef::Trait(_) = import.item_to_import.into_module_def() {
-                !ctx.exclude_flyimport.contains_key(&def)
-            } else {
-                true
-            }
-        })
+        .filter(|import| filter_excluded_flyimport(ctx, import))
         .sorted_by(|a, b| {
             let key = |import_path| {
                 (
@@ -401,6 +372,28 @@ fn import_on_the_fly_method(
     Some(())
 }
 
+fn filter_excluded_flyimport(ctx: &CompletionContext<'_>, import: &LocatedImport) -> bool {
+    let def = import.item_to_import.into_module_def();
+    let is_exclude_flyimport = ctx.exclude_flyimport.get(&def).copied();
+
+    if matches!(is_exclude_flyimport, Some(AutoImportExclusionType::Always))
+        || !import.complete_in_flyimport.0
+    {
+        return false;
+    }
+    let method_imported = import.item_to_import != import.original_item;
+    if method_imported
+        && (is_exclude_flyimport.is_some()
+            || ctx.exclude_flyimport.contains_key(&import.original_item.into_module_def()))
+    {
+        // If this is a method, exclude it either if it was excluded itself (which may not be caught above,
+        // because `item_to_import` is the trait), or if its trait was excluded. We don't need to check
+        // the attributes here, since they pass from trait to methods on import map construction.
+        return false;
+    }
+    true
+}
+
 fn import_name(ctx: &CompletionContext<'_>) -> String {
     let token_kind = ctx.token.kind();
 
diff --git a/crates/ide-completion/src/context.rs b/crates/ide-completion/src/context.rs
index 304379603199..fd25ee05e039 100644
--- a/crates/ide-completion/src/context.rs
+++ b/crates/ide-completion/src/context.rs
@@ -8,8 +8,8 @@ use std::{iter, ops::ControlFlow};
 
 use base_db::{RootQueryDb as _, salsa::AsDynDatabase};
 use hir::{
-    DisplayTarget, HasAttrs, Local, ModPath, ModuleDef, ModuleSource, Name, PathResolution,
-    ScopeDef, Semantics, SemanticsScope, Symbol, Type, TypeInfo,
+    DisplayTarget, HasAttrs, Local, ModuleDef, ModuleSource, Name, PathResolution, ScopeDef,
+    Semantics, SemanticsScope, Symbol, Type, TypeInfo,
 };
 use ide_db::{
     FilePosition, FxHashMap, FxHashSet, RootDatabase, famous_defs::FamousDefs,
@@ -796,15 +796,12 @@ impl<'a> CompletionContext<'a> {
             .exclude_traits
             .iter()
             .filter_map(|path| {
-                scope
-                    .resolve_mod_path(&ModPath::from_segments(
-                        hir::PathKind::Plain,
-                        path.split("::").map(Symbol::intern).map(Name::new_symbol_root),
-                    ))
-                    .find_map(|it| match it {
+                hir::resolve_absolute_path(db, path.split("::").map(Symbol::intern)).find_map(
+                    |it| match it {
                         hir::ItemInNs::Types(ModuleDef::Trait(t)) => Some(t),
                         _ => None,
-                    })
+                    },
+                )
             })
             .collect();
 
@@ -812,11 +809,7 @@ impl<'a> CompletionContext<'a> {
             .exclude_flyimport
             .iter()
             .flat_map(|(path, kind)| {
-                scope
-                    .resolve_mod_path(&ModPath::from_segments(
-                        hir::PathKind::Plain,
-                        path.split("::").map(Symbol::intern).map(Name::new_symbol_root),
-                    ))
+                hir::resolve_absolute_path(db, path.split("::").map(Symbol::intern))
                     .map(|it| (it.into_module_def(), *kind))
             })
             .collect();
diff --git a/crates/ide-completion/src/render.rs b/crates/ide-completion/src/render.rs
index 82c12662c433..b0adbad74ed1 100644
--- a/crates/ide-completion/src/render.rs
+++ b/crates/ide-completion/src/render.rs
@@ -334,7 +334,7 @@ pub(crate) fn render_expr(
             continue;
         };
 
-        item.add_import(LocatedImport::new(path, trait_item, trait_item));
+        item.add_import(LocatedImport::new_no_completion(path, trait_item, trait_item));
     }
 
     Some(item)
diff --git a/crates/ide-completion/src/snippet.rs b/crates/ide-completion/src/snippet.rs
index 07f33a826e4c..9dc0c0234dc5 100644
--- a/crates/ide-completion/src/snippet.rs
+++ b/crates/ide-completion/src/snippet.rs
@@ -174,7 +174,7 @@ fn import_edits(ctx: &CompletionContext<'_>, requires: &[ModPath]) -> Option<Vec
             ctx.config.insert_use.prefix_kind,
             import_cfg,
         )?;
-        Some((path.len() > 1).then(|| LocatedImport::new(path.clone(), item, item)))
+        Some((path.len() > 1).then(|| LocatedImport::new_no_completion(path.clone(), item, item)))
     };
     let mut res = Vec::with_capacity(requires.len());
     for import in requires {
diff --git a/crates/ide-completion/src/tests/expression.rs b/crates/ide-completion/src/tests/expression.rs
index 22d42ba750ed..98da2cbf0780 100644
--- a/crates/ide-completion/src/tests/expression.rs
+++ b/crates/ide-completion/src/tests/expression.rs
@@ -1555,7 +1555,10 @@ fn main() {
 #[test]
 fn excluded_trait_method_is_excluded() {
     check_with_config(
-        CompletionConfig { exclude_traits: &["test::ExcludedTrait".to_owned()], ..TEST_CONFIG },
+        CompletionConfig {
+            exclude_traits: &["ra_test_fixture::ExcludedTrait".to_owned()],
+            ..TEST_CONFIG
+        },
         r#"
 trait ExcludedTrait {
     fn foo(&self) {}
@@ -1575,23 +1578,20 @@ fn foo() {
 }
         "#,
         expect![[r#"
-            me bar() (as ExcludedTrait) fn(&self)
-            me baz() (as ExcludedTrait) fn(&self)
-            me foo() (as ExcludedTrait) fn(&self)
-            me inherent()               fn(&self)
-            sn box                 Box::new(expr)
-            sn call                function(expr)
-            sn const                     const {}
-            sn dbg                     dbg!(expr)
-            sn dbgr                   dbg!(&expr)
-            sn deref                        *expr
-            sn let                            let
-            sn letm                       let mut
-            sn match                match expr {}
-            sn ref                          &expr
-            sn refm                     &mut expr
-            sn return                 return expr
-            sn unsafe                   unsafe {}
+            me inherent() fn(&self)
+            sn box   Box::new(expr)
+            sn call  function(expr)
+            sn const       const {}
+            sn dbg       dbg!(expr)
+            sn dbgr     dbg!(&expr)
+            sn deref          *expr
+            sn let              let
+            sn letm         let mut
+            sn match  match expr {}
+            sn ref            &expr
+            sn refm       &mut expr
+            sn return   return expr
+            sn unsafe     unsafe {}
         "#]],
     );
 }
@@ -1599,7 +1599,10 @@ fn foo() {
 #[test]
 fn excluded_trait_not_excluded_when_inherent() {
     check_with_config(
-        CompletionConfig { exclude_traits: &["test::ExcludedTrait".to_owned()], ..TEST_CONFIG },
+        CompletionConfig {
+            exclude_traits: &["ra_test_fixture::ExcludedTrait".to_owned()],
+            ..TEST_CONFIG
+        },
         r#"
 trait ExcludedTrait {
     fn foo(&self) {}
@@ -1633,7 +1636,10 @@ fn foo(v: &dyn ExcludedTrait) {
         "#]],
     );
     check_with_config(
-        CompletionConfig { exclude_traits: &["test::ExcludedTrait".to_owned()], ..TEST_CONFIG },
+        CompletionConfig {
+            exclude_traits: &["ra_test_fixture::ExcludedTrait".to_owned()],
+            ..TEST_CONFIG
+        },
         r#"
 trait ExcludedTrait {
     fn foo(&self) {}
@@ -1667,7 +1673,10 @@ fn foo(v: impl ExcludedTrait) {
         "#]],
     );
     check_with_config(
-        CompletionConfig { exclude_traits: &["test::ExcludedTrait".to_owned()], ..TEST_CONFIG },
+        CompletionConfig {
+            exclude_traits: &["ra_test_fixture::ExcludedTrait".to_owned()],
+            ..TEST_CONFIG
+        },
         r#"
 trait ExcludedTrait {
     fn foo(&self) {}
@@ -1706,7 +1715,7 @@ fn foo<T: ExcludedTrait>(v: T) {
 fn excluded_trait_method_is_excluded_from_flyimport() {
     check_with_config(
         CompletionConfig {
-            exclude_traits: &["test::module2::ExcludedTrait".to_owned()],
+            exclude_traits: &["ra_test_fixture::module2::ExcludedTrait".to_owned()],
             ..TEST_CONFIG
         },
         r#"
@@ -1730,23 +1739,20 @@ fn foo() {
 }
         "#,
         expect![[r#"
-            me bar() (use module2::ExcludedTrait) fn(&self)
-            me baz() (use module2::ExcludedTrait) fn(&self)
-            me foo() (use module2::ExcludedTrait) fn(&self)
-            me inherent()                         fn(&self)
-            sn box                           Box::new(expr)
-            sn call                          function(expr)
-            sn const                               const {}
-            sn dbg                               dbg!(expr)
-            sn dbgr                             dbg!(&expr)
-            sn deref                                  *expr
-            sn let                                      let
-            sn letm                                 let mut
-            sn match                          match expr {}
-            sn ref                                    &expr
-            sn refm                               &mut expr
-            sn return                           return expr
-            sn unsafe                             unsafe {}
+            me inherent() fn(&self)
+            sn box   Box::new(expr)
+            sn call  function(expr)
+            sn const       const {}
+            sn dbg       dbg!(expr)
+            sn dbgr     dbg!(&expr)
+            sn deref          *expr
+            sn let              let
+            sn letm         let mut
+            sn match  match expr {}
+            sn ref            &expr
+            sn refm       &mut expr
+            sn return   return expr
+            sn unsafe     unsafe {}
         "#]],
     );
 }
@@ -1756,7 +1762,7 @@ fn flyimport_excluded_trait_method_is_excluded_from_flyimport() {
     check_with_config(
         CompletionConfig {
             exclude_flyimport: vec![(
-                "test::module2::ExcludedTrait".to_owned(),
+                "ra_test_fixture::module2::ExcludedTrait".to_owned(),
                 AutoImportExclusionType::Methods,
             )],
             ..TEST_CONFIG
@@ -1782,23 +1788,20 @@ fn foo() {
 }
         "#,
         expect![[r#"
-            me bar() (use module2::ExcludedTrait) fn(&self)
-            me baz() (use module2::ExcludedTrait) fn(&self)
-            me foo() (use module2::ExcludedTrait) fn(&self)
-            me inherent()                         fn(&self)
-            sn box                           Box::new(expr)
-            sn call                          function(expr)
-            sn const                               const {}
-            sn dbg                               dbg!(expr)
-            sn dbgr                             dbg!(&expr)
-            sn deref                                  *expr
-            sn let                                      let
-            sn letm                                 let mut
-            sn match                          match expr {}
-            sn ref                                    &expr
-            sn refm                               &mut expr
-            sn return                           return expr
-            sn unsafe                             unsafe {}
+            me inherent() fn(&self)
+            sn box   Box::new(expr)
+            sn call  function(expr)
+            sn const       const {}
+            sn dbg       dbg!(expr)
+            sn dbgr     dbg!(&expr)
+            sn deref          *expr
+            sn let              let
+            sn letm         let mut
+            sn match  match expr {}
+            sn ref            &expr
+            sn refm       &mut expr
+            sn return   return expr
+            sn unsafe     unsafe {}
         "#]],
     );
 }
@@ -1806,7 +1809,10 @@ fn foo() {
 #[test]
 fn excluded_trait_method_is_excluded_from_path_completion() {
     check_with_config(
-        CompletionConfig { exclude_traits: &["test::ExcludedTrait".to_owned()], ..TEST_CONFIG },
+        CompletionConfig {
+            exclude_traits: &["ra_test_fixture::ExcludedTrait".to_owned()],
+            ..TEST_CONFIG
+        },
         r#"
 pub trait ExcludedTrait {
     fn foo(&self) {}
@@ -1826,10 +1832,7 @@ fn foo() {
 }
         "#,
         expect![[r#"
-            me bar(…) (as ExcludedTrait) fn(&self)
-            me baz(…) (as ExcludedTrait) fn(&self)
-            me foo(…) (as ExcludedTrait) fn(&self)
-            me inherent(…)               fn(&self)
+            me inherent(…) fn(&self)
         "#]],
     );
 }
@@ -1837,7 +1840,10 @@ fn foo() {
 #[test]
 fn excluded_trait_method_is_not_excluded_when_trait_is_specified() {
     check_with_config(
-        CompletionConfig { exclude_traits: &["test::ExcludedTrait".to_owned()], ..TEST_CONFIG },
+        CompletionConfig {
+            exclude_traits: &["ra_test_fixture::ExcludedTrait".to_owned()],
+            ..TEST_CONFIG
+        },
         r#"
 pub trait ExcludedTrait {
     fn foo(&self) {}
@@ -1863,7 +1869,10 @@ fn foo() {
             "#]],
     );
     check_with_config(
-        CompletionConfig { exclude_traits: &["test::ExcludedTrait".to_owned()], ..TEST_CONFIG },
+        CompletionConfig {
+            exclude_traits: &["ra_test_fixture::ExcludedTrait".to_owned()],
+            ..TEST_CONFIG
+        },
         r#"
 pub trait ExcludedTrait {
     fn foo(&self) {}
@@ -1893,7 +1902,10 @@ fn foo() {
 #[test]
 fn excluded_trait_not_excluded_when_inherent_path() {
     check_with_config(
-        CompletionConfig { exclude_traits: &["test::ExcludedTrait".to_owned()], ..TEST_CONFIG },
+        CompletionConfig {
+            exclude_traits: &["ra_test_fixture::ExcludedTrait".to_owned()],
+            ..TEST_CONFIG
+        },
         r#"
 trait ExcludedTrait {
     fn foo(&self) {}
@@ -1914,7 +1926,10 @@ fn foo() {
         "#]],
     );
     check_with_config(
-        CompletionConfig { exclude_traits: &["test::ExcludedTrait".to_owned()], ..TEST_CONFIG },
+        CompletionConfig {
+            exclude_traits: &["ra_test_fixture::ExcludedTrait".to_owned()],
+            ..TEST_CONFIG
+        },
         r#"
 trait ExcludedTrait {
     fn foo(&self) {}
diff --git a/crates/ide-completion/src/tests/flyimport.rs b/crates/ide-completion/src/tests/flyimport.rs
index 8bba44c12bf6..27c91bc7c455 100644
--- a/crates/ide-completion/src/tests/flyimport.rs
+++ b/crates/ide-completion/src/tests/flyimport.rs
@@ -1810,9 +1810,10 @@ fn function() {
 
 #[test]
 fn excluded_trait_item_included_when_exact_match() {
+    // FIXME: This does not work, we need to change the code.
     check_with_config(
         CompletionConfig {
-            exclude_traits: &["test::module2::ExcludedTrait".to_owned()],
+            exclude_traits: &["ra_test_fixture::module2::ExcludedTrait".to_owned()],
             ..TEST_CONFIG
         },
         r#"
@@ -1828,10 +1829,122 @@ mod module2 {
 
 fn foo() {
     true.foo$0
+}
+        "#,
+        expect![""],
+    );
+}
+
+#[test]
+fn excluded_via_attr() {
+    check(
+        r#"
+mod module2 {
+    #[rust_analyzer::completions(ignore_flyimport)]
+    pub trait ExcludedTrait {
+        fn foo(&self) {}
+        fn bar(&self) {}
+        fn baz(&self) {}
+    }
+
+    impl<T> ExcludedTrait for T {}
+}
+
+fn foo() {
+    true.$0
+}
+        "#,
+        expect![""],
+    );
+    check(
+        r#"
+mod module2 {
+    #[rust_analyzer::completions(ignore_flyimport_methods)]
+    pub trait ExcludedTrait {
+        fn foo(&self) {}
+        fn bar(&self) {}
+        fn baz(&self) {}
+    }
+
+    impl<T> ExcludedTrait for T {}
+}
+
+fn foo() {
+    true.$0
+}
+        "#,
+        expect![""],
+    );
+    check(
+        r#"
+mod module2 {
+    #[rust_analyzer::completions(ignore_methods)]
+    pub trait ExcludedTrait {
+        fn foo(&self) {}
+        fn bar(&self) {}
+        fn baz(&self) {}
+    }
+
+    impl<T> ExcludedTrait for T {}
+}
+
+fn foo() {
+    true.$0
+}
+        "#,
+        expect![""],
+    );
+    check(
+        r#"
+mod module2 {
+    #[rust_analyzer::completions(ignore_flyimport)]
+    pub trait ExcludedTrait {
+        fn foo(&self) {}
+        fn bar(&self) {}
+        fn baz(&self) {}
+    }
+
+    impl<T> ExcludedTrait for T {}
+}
+
+fn foo() {
+    ExcludedTrait$0
+}
+        "#,
+        expect![""],
+    );
+    check(
+        r#"
+mod module2 {
+    #[rust_analyzer::completions(ignore_methods)]
+    pub trait ExcludedTrait {
+        fn foo(&self) {}
+        fn bar(&self) {}
+        fn baz(&self) {}
+    }
+
+    impl<T> ExcludedTrait for T {}
+}
+
+fn foo() {
+    ExcludedTrait$0
 }
         "#,
         expect![[r#"
-            me foo() (use module2::ExcludedTrait) fn(&self)
+            tt ExcludedTrait (use module2::ExcludedTrait)
         "#]],
     );
+    check(
+        r#"
+mod module2 {
+    #[rust_analyzer::completions(ignore_flyimport)]
+    pub struct Foo {}
+}
+
+fn foo() {
+    Foo$0
+}
+        "#,
+        expect![""],
+    );
 }
diff --git a/crates/ide-db/src/imports/import_assets.rs b/crates/ide-db/src/imports/import_assets.rs
index 650c957ca458..ac592dfe93cf 100644
--- a/crates/ide-db/src/imports/import_assets.rs
+++ b/crates/ide-db/src/imports/import_assets.rs
@@ -3,8 +3,8 @@
 use std::ops::ControlFlow;
 
 use hir::{
-    AsAssocItem, AssocItem, AssocItemContainer, Crate, HasCrate, ImportPathConfig, ItemInNs,
-    ModPath, Module, ModuleDef, Name, PathResolution, PrefixKind, ScopeDef, Semantics,
+    AsAssocItem, AssocItem, AssocItemContainer, Complete, Crate, HasCrate, ImportPathConfig,
+    ItemInNs, ModPath, Module, ModuleDef, Name, PathResolution, PrefixKind, ScopeDef, Semantics,
     SemanticsScope, Trait, TyFingerprint, Type, db::HirDatabase,
 };
 use itertools::Itertools;
@@ -183,6 +183,9 @@ impl ImportAssets {
     }
 }
 
+#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
+pub struct CompleteInFlyimport(pub bool);
+
 /// An import (not necessary the only one) that corresponds a certain given [`PathImportCandidate`].
 /// (the structure is not entirely correct, since there can be situations requiring two imports, see FIXME below for the details)
 #[derive(Debug, Clone, PartialEq, Eq, Hash)]
@@ -198,11 +201,31 @@ pub struct LocatedImport {
     /// the original item is the associated constant, but the import has to be a trait that
     /// defines this constant.
     pub original_item: ItemInNs,
+    /// The value of `#[rust_analyzer::completions(...)]`, if existing.
+    pub complete_in_flyimport: CompleteInFlyimport,
 }
 
 impl LocatedImport {
-    pub fn new(import_path: ModPath, item_to_import: ItemInNs, original_item: ItemInNs) -> Self {
-        Self { import_path, item_to_import, original_item }
+    pub fn new(
+        import_path: ModPath,
+        item_to_import: ItemInNs,
+        original_item: ItemInNs,
+        complete_in_flyimport: CompleteInFlyimport,
+    ) -> Self {
+        Self { import_path, item_to_import, original_item, complete_in_flyimport }
+    }
+
+    pub fn new_no_completion(
+        import_path: ModPath,
+        item_to_import: ItemInNs,
+        original_item: ItemInNs,
+    ) -> Self {
+        Self {
+            import_path,
+            item_to_import,
+            original_item,
+            complete_in_flyimport: CompleteInFlyimport(true),
+        }
     }
 }
 
@@ -351,12 +374,17 @@ fn path_applicable_imports(
                 // see also an ignored test under FIXME comment in the qualify_path.rs module
                 AssocSearchMode::Exclude,
             )
-            .filter_map(|item| {
+            .filter_map(|(item, do_not_complete)| {
                 if !scope_filter(item) {
                     return None;
                 }
                 let mod_path = mod_path(item)?;
-                Some(LocatedImport::new(mod_path, item, item))
+                Some(LocatedImport::new(
+                    mod_path,
+                    item,
+                    item,
+                    CompleteInFlyimport(do_not_complete != Complete::IgnoreFlyimport),
+                ))
             })
             .take(DEFAULT_QUERY_SEARCH_LIMIT)
             .collect()
@@ -371,7 +399,7 @@ fn path_applicable_imports(
             NameToImport::Exact(first_qsegment.as_str().to_owned(), true),
             AssocSearchMode::Exclude,
         )
-        .filter_map(|item| {
+        .filter_map(|(item, do_not_complete)| {
             // we found imports for `first_qsegment`, now we need to filter these imports by whether
             // they result in resolving the rest of the path successfully
             validate_resolvable(
@@ -382,6 +410,7 @@ fn path_applicable_imports(
                 &path_candidate.name,
                 item,
                 qualifier_rest,
+                CompleteInFlyimport(do_not_complete != Complete::IgnoreFlyimport),
             )
         })
         .take(DEFAULT_QUERY_SEARCH_LIMIT)
@@ -399,6 +428,7 @@ fn validate_resolvable(
     candidate: &NameToImport,
     resolved_qualifier: ItemInNs,
     unresolved_qualifier: &[Name],
+    complete_in_flyimport: CompleteInFlyimport,
 ) -> Option<LocatedImport> {
     let _p = tracing::info_span!("ImportAssets::import_for_item").entered();
 
@@ -434,7 +464,14 @@ fn validate_resolvable(
                     false => ControlFlow::Continue(()),
                 },
             )
-            .map(|item| LocatedImport::new(import_path_candidate, resolved_qualifier, item));
+            .map(|item| {
+                LocatedImport::new(
+                    import_path_candidate,
+                    resolved_qualifier,
+                    item,
+                    complete_in_flyimport,
+                )
+            });
         }
         // FIXME
         ModuleDef::Trait(_) => return None,
@@ -472,6 +509,7 @@ fn validate_resolvable(
             import_path_candidate.clone(),
             resolved_qualifier,
             assoc_to_item(assoc),
+            complete_in_flyimport,
         ))
     })
 }
@@ -510,15 +548,15 @@ fn trait_applicable_items(
     let env_traits = trait_candidate.receiver_ty.env_traits(db);
     let related_traits = inherent_traits.chain(env_traits).collect::<FxHashSet<_>>();
 
-    let mut required_assoc_items = FxHashSet::default();
+    let mut required_assoc_items = FxHashMap::default();
     let mut trait_candidates: FxHashSet<_> = items_locator::items_with_name(
         db,
         current_crate,
         trait_candidate.assoc_item_name.clone(),
         AssocSearchMode::AssocItemsOnly,
     )
-    .filter_map(|input| item_as_assoc(db, input))
-    .filter_map(|assoc| {
+    .filter_map(|(input, do_not_complete)| Some((item_as_assoc(db, input)?, do_not_complete)))
+    .filter_map(|(assoc, do_not_complete)| {
         if !trait_assoc_item && matches!(assoc, AssocItem::Const(_) | AssocItem::TypeAlias(_)) {
             return None;
         }
@@ -527,7 +565,8 @@ fn trait_applicable_items(
         if related_traits.contains(&assoc_item_trait) {
             return None;
         }
-        required_assoc_items.insert(assoc);
+        required_assoc_items
+            .insert(assoc, CompleteInFlyimport(do_not_complete != Complete::IgnoreFlyimport));
         Some(assoc_item_trait.into())
     })
     .collect();
@@ -599,7 +638,7 @@ fn trait_applicable_items(
             None,
             None,
             |assoc| {
-                if required_assoc_items.contains(&assoc) {
+                if let Some(&complete_in_flyimport) = required_assoc_items.get(&assoc) {
                     let located_trait = assoc.container_trait(db).filter(|&it| scope_filter(it))?;
                     let trait_item = ItemInNs::from(ModuleDef::from(located_trait));
                     let import_path = trait_import_paths
@@ -610,6 +649,7 @@ fn trait_applicable_items(
                         import_path,
                         trait_item,
                         assoc_to_item(assoc),
+                        complete_in_flyimport,
                     ));
                 }
                 None::<()>
@@ -624,7 +664,7 @@ fn trait_applicable_items(
             None,
             |function| {
                 let assoc = function.as_assoc_item(db)?;
-                if required_assoc_items.contains(&assoc) {
+                if let Some(&complete_in_flyimport) = required_assoc_items.get(&assoc) {
                     let located_trait = assoc.container_trait(db).filter(|&it| scope_filter(it))?;
                     let trait_item = ItemInNs::from(ModuleDef::from(located_trait));
                     let import_path = trait_import_paths
@@ -635,6 +675,7 @@ fn trait_applicable_items(
                         import_path,
                         trait_item,
                         assoc_to_item(assoc),
+                        complete_in_flyimport,
                     ));
                 }
                 None::<()>
diff --git a/crates/ide-db/src/items_locator.rs b/crates/ide-db/src/items_locator.rs
index 7a543d64e27f..e9385253250a 100644
--- a/crates/ide-db/src/items_locator.rs
+++ b/crates/ide-db/src/items_locator.rs
@@ -5,7 +5,7 @@
 use std::ops::ControlFlow;
 
 use either::Either;
-use hir::{Crate, ItemInNs, Module, import_map};
+use hir::{Complete, Crate, ItemInNs, Module, import_map};
 
 use crate::{
     RootDatabase,
@@ -25,7 +25,7 @@ pub fn items_with_name(
     krate: Crate,
     name: NameToImport,
     assoc_item_search: AssocSearchMode,
-) -> impl Iterator<Item = ItemInNs> {
+) -> impl Iterator<Item = (ItemInNs, Complete)> {
     let _p = tracing::info_span!("items_with_name", name = name.text(), assoc_item_search = ?assoc_item_search, crate = ?krate.display_name(db).map(|name| name.to_string()))
         .entered();
 
@@ -123,26 +123,29 @@ fn find_items(
     krate: Crate,
     local_query: symbol_index::Query,
     external_query: import_map::Query,
-) -> impl Iterator<Item = ItemInNs> {
+) -> impl Iterator<Item = (ItemInNs, Complete)> {
     let _p = tracing::info_span!("find_items").entered();
 
     // NOTE: `external_query` includes `assoc_item_search`, so we don't need to
     // filter on our own.
-    let external_importables =
-        krate.query_external_importables(db, external_query).map(|external_importable| {
-            match external_importable {
+    let external_importables = krate.query_external_importables(db, external_query).map(
+        |(external_importable, do_not_complete)| {
+            let external_importable = match external_importable {
                 Either::Left(module_def) => ItemInNs::from(module_def),
                 Either::Right(macro_def) => ItemInNs::from(macro_def),
-            }
-        });
+            };
+            (external_importable, do_not_complete)
+        },
+    );
 
     // Query the local crate using the symbol index.
     let mut local_results = Vec::new();
     local_query.search(&symbol_index::crate_symbols(db, krate), |local_candidate| {
-        local_results.push(match local_candidate.def {
+        let def = match local_candidate.def {
             hir::ModuleDef::Macro(macro_def) => ItemInNs::Macros(macro_def),
             def => ItemInNs::from(def),
-        });
+        };
+        local_results.push((def, local_candidate.do_not_complete));
         ControlFlow::<()>::Continue(())
     });
     local_results.into_iter().chain(external_importables)
diff --git a/crates/ide-db/src/test_data/test_doc_alias.txt b/crates/ide-db/src/test_data/test_doc_alias.txt
index ea50745d6793..8e342ec553c2 100644
--- a/crates/ide-db/src/test_data/test_doc_alias.txt
+++ b/crates/ide-db/src/test_data/test_doc_alias.txt
@@ -42,6 +42,7 @@
                 container_name: None,
                 is_alias: false,
                 is_assoc: false,
+                do_not_complete: Yes,
             },
             FileSymbol {
                 name: "Struct",
@@ -75,6 +76,7 @@
                 container_name: None,
                 is_alias: false,
                 is_assoc: false,
+                do_not_complete: Yes,
             },
             FileSymbol {
                 name: "mul1",
@@ -108,6 +110,7 @@
                 container_name: None,
                 is_alias: true,
                 is_assoc: false,
+                do_not_complete: Yes,
             },
             FileSymbol {
                 name: "mul2",
@@ -141,6 +144,7 @@
                 container_name: None,
                 is_alias: true,
                 is_assoc: false,
+                do_not_complete: Yes,
             },
             FileSymbol {
                 name: "s1",
@@ -174,6 +178,7 @@
                 container_name: None,
                 is_alias: true,
                 is_assoc: false,
+                do_not_complete: Yes,
             },
             FileSymbol {
                 name: "s1",
@@ -207,6 +212,7 @@
                 container_name: None,
                 is_alias: true,
                 is_assoc: false,
+                do_not_complete: Yes,
             },
             FileSymbol {
                 name: "s2",
@@ -240,6 +246,7 @@
                 container_name: None,
                 is_alias: true,
                 is_assoc: false,
+                do_not_complete: Yes,
             },
         ],
     ),
diff --git a/crates/ide-db/src/test_data/test_symbol_index_collection.txt b/crates/ide-db/src/test_data/test_symbol_index_collection.txt
index d2d24262f098..6de25c000e52 100644
--- a/crates/ide-db/src/test_data/test_symbol_index_collection.txt
+++ b/crates/ide-db/src/test_data/test_symbol_index_collection.txt
@@ -40,6 +40,7 @@
                 container_name: None,
                 is_alias: false,
                 is_assoc: false,
+                do_not_complete: Yes,
             },
             FileSymbol {
                 name: "CONST",
@@ -71,6 +72,7 @@
                 container_name: None,
                 is_alias: false,
                 is_assoc: false,
+                do_not_complete: Yes,
             },
             FileSymbol {
                 name: "CONST_WITH_INNER",
@@ -102,6 +104,7 @@
                 container_name: None,
                 is_alias: false,
                 is_assoc: false,
+                do_not_complete: Yes,
             },
             FileSymbol {
                 name: "Enum",
@@ -135,6 +138,7 @@
                 container_name: None,
                 is_alias: false,
                 is_assoc: false,
+                do_not_complete: Yes,
             },
             FileSymbol {
                 name: "ItemLikeMacro",
@@ -168,6 +172,7 @@
                 container_name: None,
                 is_alias: false,
                 is_assoc: false,
+                do_not_complete: Yes,
             },
             FileSymbol {
                 name: "Macro",
@@ -201,6 +206,7 @@
                 container_name: None,
                 is_alias: false,
                 is_assoc: false,
+                do_not_complete: Yes,
             },
             FileSymbol {
                 name: "STATIC",
@@ -232,6 +238,7 @@
                 container_name: None,
                 is_alias: false,
                 is_assoc: false,
+                do_not_complete: Yes,
             },
             FileSymbol {
                 name: "Struct",
@@ -265,6 +272,7 @@
                 container_name: None,
                 is_alias: false,
                 is_assoc: false,
+                do_not_complete: Yes,
             },
             FileSymbol {
                 name: "StructFromMacro",
@@ -295,6 +303,7 @@
                 container_name: None,
                 is_alias: false,
                 is_assoc: false,
+                do_not_complete: Yes,
             },
             FileSymbol {
                 name: "StructInFn",
@@ -330,6 +339,7 @@
                 ),
                 is_alias: false,
                 is_assoc: false,
+                do_not_complete: Yes,
             },
             FileSymbol {
                 name: "StructInNamedConst",
@@ -365,6 +375,7 @@
                 ),
                 is_alias: false,
                 is_assoc: false,
+                do_not_complete: Yes,
             },
             FileSymbol {
                 name: "StructInUnnamedConst",
@@ -398,6 +409,7 @@
                 container_name: None,
                 is_alias: false,
                 is_assoc: false,
+                do_not_complete: Yes,
             },
             FileSymbol {
                 name: "StructT",
@@ -431,6 +443,7 @@
                 container_name: None,
                 is_alias: false,
                 is_assoc: false,
+                do_not_complete: Yes,
             },
             FileSymbol {
                 name: "Trait",
@@ -462,6 +475,7 @@
                 container_name: None,
                 is_alias: false,
                 is_assoc: false,
+                do_not_complete: Yes,
             },
             FileSymbol {
                 name: "Trait",
@@ -495,6 +509,7 @@
                 container_name: None,
                 is_alias: false,
                 is_assoc: false,
+                do_not_complete: Yes,
             },
             FileSymbol {
                 name: "Union",
@@ -528,6 +543,7 @@
                 container_name: None,
                 is_alias: false,
                 is_assoc: false,
+                do_not_complete: Yes,
             },
             FileSymbol {
                 name: "a_mod",
@@ -563,6 +579,7 @@
                 container_name: None,
                 is_alias: false,
                 is_assoc: false,
+                do_not_complete: Yes,
             },
             FileSymbol {
                 name: "b_mod",
@@ -598,6 +615,7 @@
                 container_name: None,
                 is_alias: false,
                 is_assoc: false,
+                do_not_complete: Yes,
             },
             FileSymbol {
                 name: "define_struct",
@@ -631,6 +649,7 @@
                 container_name: None,
                 is_alias: false,
                 is_assoc: false,
+                do_not_complete: Yes,
             },
             FileSymbol {
                 name: "generic_impl_fn",
@@ -664,6 +683,7 @@
                 ),
                 is_alias: false,
                 is_assoc: true,
+                do_not_complete: Yes,
             },
             FileSymbol {
                 name: "impl_fn",
@@ -697,6 +717,7 @@
                 ),
                 is_alias: false,
                 is_assoc: true,
+                do_not_complete: Yes,
             },
             FileSymbol {
                 name: "macro_rules_macro",
@@ -730,6 +751,7 @@
                 container_name: None,
                 is_alias: false,
                 is_assoc: false,
+                do_not_complete: Yes,
             },
             FileSymbol {
                 name: "main",
@@ -761,6 +783,7 @@
                 container_name: None,
                 is_alias: false,
                 is_assoc: false,
+                do_not_complete: Yes,
             },
             FileSymbol {
                 name: "really_define_struct",
@@ -794,6 +817,7 @@
                 container_name: None,
                 is_alias: false,
                 is_assoc: false,
+                do_not_complete: Yes,
             },
             FileSymbol {
                 name: "trait_fn",
@@ -827,6 +851,7 @@
                 ),
                 is_alias: false,
                 is_assoc: true,
+                do_not_complete: Yes,
             },
         ],
     ),
@@ -873,6 +898,7 @@
                 container_name: None,
                 is_alias: false,
                 is_assoc: false,
+                do_not_complete: Yes,
             },
         ],
     ),
@@ -917,6 +943,7 @@
                 container_name: None,
                 is_alias: false,
                 is_assoc: false,
+                do_not_complete: Yes,
             },
             FileSymbol {
                 name: "IsThisJustATrait",
@@ -950,6 +977,7 @@
                 container_name: None,
                 is_alias: false,
                 is_assoc: false,
+                do_not_complete: Yes,
             },
             FileSymbol {
                 name: "StructInModB",
@@ -983,6 +1011,7 @@
                 container_name: None,
                 is_alias: false,
                 is_assoc: false,
+                do_not_complete: Yes,
             },
             FileSymbol {
                 name: "SuperItemLikeMacro",
@@ -1016,6 +1045,7 @@
                 container_name: None,
                 is_alias: false,
                 is_assoc: false,
+                do_not_complete: Yes,
             },
             FileSymbol {
                 name: "ThisStruct",
@@ -1049,6 +1079,7 @@
                 container_name: None,
                 is_alias: false,
                 is_assoc: false,
+                do_not_complete: Yes,
             },
         ],
     ),
diff --git a/crates/intern/src/symbol/symbols.rs b/crates/intern/src/symbol/symbols.rs
index cc9b3ef45736..4841f4865915 100644
--- a/crates/intern/src/symbol/symbols.rs
+++ b/crates/intern/src/symbol/symbols.rs
@@ -521,4 +521,8 @@ define_symbols! {
     win64,
     array,
     boxed_slice,
+    completions,
+    ignore_flyimport,
+    ignore_flyimport_methods,
+    ignore_methods,
 }


Reviews:
user: login: Veykril
body: Not a fan of this infesting all of hir-def and the like with ide specific things (though I do like the changing of flags to all use `bitflags`).

Given this only affects flyimport (with the exception of traits), I feel like this would be more fitting to be recorded as a flag in the `ImportMap` `ImportInfo` entries and then either be filtered immediately in `search_for_imports` or have that flag propagate out of `search_for_imports`.
state: CHANGES_REQUESTED
author_association: MEMBER
submitted_at: 2025-03-16T17:47:08Z
user: login: Veykril
state: COMMENTED
author_association: MEMBER
submitted_at: 2025-03-16T17:48:17Z
user: login: ChayimFriedman2
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-03-16T19:00:21Z
user: login: Veykril
state: COMMENTED
author_association: MEMBER
submitted_at: 2025-03-17T12:49:06Z
user: login: Veykril
state: COMMENTED
author_association: MEMBER
submitted_at: 2025-03-17T14:39:37Z
user: login: ChayimFriedman2
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-03-17T17:43:19Z
user: login: Veykril
state: COMMENTED
author_association: MEMBER
submitted_at: 2025-03-23T12:34:27Z
user: login: ChayimFriedman2
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-03-27T02:56:20Z
user: login: Veykril
state: COMMENTED
author_association: MEMBER
submitted_at: 2025-03-27T06:15:40Z
user: login: ChayimFriedman2
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-03-27T12:01:32Z
user: login: Veykril
state: APPROVED
author_association: MEMBER
submitted_at: 2025-03-27T12:32:19Z

Review comments:
diff_hunk: @@ -6343,3 +6483,42 @@ where
         self(item)
     }
 }
+
+pub fn resolve_absolute_path(
path: crates/hir/src/lib.rs
user: login: Veykril
body: This method does not make sense, a crate name only exists as a name of a dependency edge. That is we can only resolve paths from within the context of some crate.
created_at: 2025-03-16T17:39:23Z
author_association: MEMBER
diff_hunk: @@ -796,27 +796,20 @@ impl<'a> CompletionContext<'a> {
             .exclude_traits
             .iter()
             .filter_map(|path| {
-                scope
-                    .resolve_mod_path(&ModPath::from_segments(
-                        hir::PathKind::Plain,
-                        path.split("::").map(Symbol::intern).map(Name::new_symbol_root),
-                    ))
-                    .find_map(|it| match it {
+                hir::resolve_absolute_path(db, path.split("::").map(Symbol::intern)).find_map(
+                    |it| match it {
                         hir::ItemInNs::Types(ModuleDef::Trait(t)) => Some(t),
                         _ => None,
-                    })
+                    },
+                )
path: crates/ide-completion/src/context.rs
user: login: Veykril
body: Why this change?
created_at: 2025-03-16T17:39:57Z
author_association: MEMBER
diff_hunk: @@ -796,27 +796,20 @@ impl<'a> CompletionContext<'a> {
             .exclude_traits
             .iter()
             .filter_map(|path| {
-                scope
-                    .resolve_mod_path(&ModPath::from_segments(
-                        hir::PathKind::Plain,
-                        path.split("::").map(Symbol::intern).map(Name::new_symbol_root),
-                    ))
-                    .find_map(|it| match it {
+                hir::resolve_absolute_path(db, path.split("::").map(Symbol::intern)).find_map(
+                    |it| match it {
                         hir::ItemInNs::Types(ModuleDef::Trait(t)) => Some(t),
                         _ => None,
-                    })
+                    },
+                )
path: crates/ide-completion/src/context.rs
user: login: Veykril
body: Ah I see, that is probably regarding

> Also fix a bug with existing settings for that where the paths wouldn't resolve correctly. @Veykril it seems you just blessed the tests incorrectly in https://github.com/rust-lang/rust-analyzer/pull/18179?

No that was intentional, we need to resolve the paths from the given scope. Though I suppose that `hir::PathKind::Plain` really should've been `hir::PathKind::Absolute`?
created_at: 2025-03-16T17:48:17Z
author_association: MEMBER
diff_hunk: @@ -796,27 +796,20 @@ impl<'a> CompletionContext<'a> {
             .exclude_traits
             .iter()
             .filter_map(|path| {
-                scope
-                    .resolve_mod_path(&ModPath::from_segments(
-                        hir::PathKind::Plain,
-                        path.split("::").map(Symbol::intern).map(Name::new_symbol_root),
-                    ))
-                    .find_map(|it| match it {
+                hir::resolve_absolute_path(db, path.split("::").map(Symbol::intern)).find_map(
+                    |it| match it {
                         hir::ItemInNs::Types(ModuleDef::Trait(t)) => Some(t),
                         _ => None,
-                    })
+                    },
+                )
path: crates/ide-completion/src/context.rs
user: login: ChayimFriedman2
body: The test blessing was probably not intentional, as it rendered those tests meaningless.

About

> This method does not make sense, a crate name only exists as a name of a dependency edge. That is we can only resolve paths from within the context of some crate.

Generally I agree, but this is a global setting, so it doesn't depend at the current crate. At the extreme what could happen is that in different crates it will resolve to different things. More problematic, this prevents marking an item from the crate itself, as seen by the tests (without hacks). Also, if some crate reexports another crate, you will have to either write both or let the setting be ignored in some places.
created_at: 2025-03-16T19:00:20Z
author_association: CONTRIBUTOR
diff_hunk: @@ -6343,3 +6352,42 @@ where
         self(item)
     }
 }
+
+pub fn resolve_absolute_path(
+    db: &dyn HirDatabase,
+    mut segments: impl Iterator<Item = Symbol>,
+) -> impl Iterator<Item = ItemInNs> {
+    segments
+        .next()
+        .and_then(|crate_name| {
+            let krate = *db.all_crates().iter().find(|&krate| {
path: crates/hir/src/lib.rs
user: login: Veykril
body: This doesn't handle if we have the same name in here multiple times, e.g. different versions of the same crate
created_at: 2025-03-17T12:46:05Z
author_association: MEMBER
diff_hunk: @@ -6343,3 +6352,42 @@ where
         self(item)
     }
 }
+
+pub fn resolve_absolute_path(
+    db: &dyn HirDatabase,
+    mut segments: impl Iterator<Item = Symbol>,
+) -> impl Iterator<Item = ItemInNs> {
+    segments
+        .next()
+        .and_then(|crate_name| {
+            let krate = *db.all_crates().iter().find(|&krate| {
+                krate
+                    .extra_data(db)
+                    .display_name
+                    .as_ref()
+                    .is_some_and(|name| *name.crate_name().symbol() == crate_name)
+            })?;
+            let mut def_map = db.crate_def_map(krate);
+            let mut module = &def_map[DefMap::ROOT];
+            let mut segments = segments.with_position().peekable();
+            while let Some((_, segment)) = segments.next_if(|&(position, _)| {
+                !matches!(position, itertools::Position::Last | itertools::Position::Only)
+            }) {
+                let res = module
+                    .scope
+                    .get_including_legacy_macros(&Name::new_symbol_root(segment))
path: crates/hir/src/lib.rs
user: login: Veykril
body: It feels wrong to me that we consider legacy scoped macros. These are never accessible outside their own crate anyways (if they are re-exported they won't be treated as legacy macros anymore but as proper item scoped macros). So this handling looks unnecssary ot me
created_at: 2025-03-17T12:47:08Z
author_association: MEMBER
diff_hunk: @@ -796,27 +796,20 @@ impl<'a> CompletionContext<'a> {
             .exclude_traits
             .iter()
             .filter_map(|path| {
-                scope
-                    .resolve_mod_path(&ModPath::from_segments(
-                        hir::PathKind::Plain,
-                        path.split("::").map(Symbol::intern).map(Name::new_symbol_root),
-                    ))
-                    .find_map(|it| match it {
+                hir::resolve_absolute_path(db, path.split("::").map(Symbol::intern)).find_map(
+                    |it| match it {
                         hir::ItemInNs::Types(ModuleDef::Trait(t)) => Some(t),
                         _ => None,
-                    })
+                    },
+                )
path: crates/ide-completion/src/context.rs
user: login: Veykril
body: Fair enough
created_at: 2025-03-17T14:39:37Z
author_association: MEMBER
diff_hunk: @@ -6343,3 +6352,42 @@ where
         self(item)
     }
 }
+
+pub fn resolve_absolute_path(
+    db: &dyn HirDatabase,
+    mut segments: impl Iterator<Item = Symbol>,
+) -> impl Iterator<Item = ItemInNs> {
+    segments
+        .next()
+        .and_then(|crate_name| {
+            let krate = *db.all_crates().iter().find(|&krate| {
path: crates/hir/src/lib.rs
user: login: ChayimFriedman2
body: That is indeed a problem. I think the best solution will to resolve to all of them.
created_at: 2025-03-17T17:43:19Z
author_association: CONTRIBUTOR
diff_hunk: @@ -1508,3 +1509,79 @@ pub struct UnresolvedMacro {
 
 #[derive(Default, Debug, Eq, PartialEq, Clone, Copy)]
 pub struct SyntheticSyntax;
+
+// Feature: Do Not Complete Attribute
+// Crate authors can opt their type out of completions in some cases.
+// This is done with the `#[rust_analyzer::do_not_complete(...)]` attribute.
+//
+// All completeable things support `#[rust_analyzer::do_not_complete(flyimport)]`,
+// which causes the thing to get excluded from flyimport completion. It will still
+// be completed when in scope. This is analogous to the setting `rust-analyzer.completion.autoimport.exclude`
+// with `"type": "always"`.
+//
+// In addition, traits support two more modes: `#[rust_analyzer::do_not_complete(methods_flyimport)]`,
+// which means the trait itself may still be flyimported but its methods won't, and
+// `#[rust_analyzer::do_not_complete(methods)]`, which means the methods won't be completed even when
+// the trait is in scope (but the trait itself may still be completed). The methods will still be completed
+// on `dyn Trait`, `impl Trait` or where the trait is specified in bounds. These modes correspond to
+// the settings `rust-analyzer.completion.autoimport.exclude` with `"type": "methods"` and
+// `rust-analyzer.completion.excludeTraits`, respectively.
+//
+// Malformed attributes will be ignored without warnings.
+//
+// Note that users have no way to override this attribute, so be careful and only include things
+// users definitely do not want to be completed!
+
+/// `#[rust_analyzer::do_not_complete(...)]` options.
+#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
+pub enum DoNotComplete {
+    /// No `#[rust_analyzer::do_not_complete(...)]`.
+    Complete,
+    /// `#[rust_analyzer::do_not_complete(flyimport)]`.
+    Flyimport,
+    /// `#[rust_analyzer::do_not_complete(methods_flyimport)]` (on a trait only).
+    MethodsFlyimport,
+    /// `#[rust_analyzer::do_not_complete(methods)]` (on a trait only).
+    Methods,
+}
path: crates/hir-def/src/lib.rs
user: login: Veykril
body: Okay how about?
```rust
/// `#[rust_analyzer::completions(...)]` options.
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
pub enum Complete {
    /// No `#[rust_analyzer::completions(...)]`.
    Yes,
    /// `#[rust_analyzer::completions(ignore_flyimport)]`.
    IgnoreFlyimport,
    /// `#[rust_analyzer::completions(flyimport_ignore_methods)]` (on a trait only).
    IgnoreFlyimportMethods,
    /// `#[rust_analyzer::completions(ignore_methods)]` (on a trait only).
    IgnoreMethods,
}
```
that is a bit more general for future `completions` modifiers that may be positive / modifying
created_at: 2025-03-23T12:34:27Z
author_association: MEMBER
diff_hunk: @@ -1508,3 +1509,79 @@ pub struct UnresolvedMacro {
 
 #[derive(Default, Debug, Eq, PartialEq, Clone, Copy)]
 pub struct SyntheticSyntax;
+
+// Feature: Do Not Complete Attribute
+// Crate authors can opt their type out of completions in some cases.
+// This is done with the `#[rust_analyzer::do_not_complete(...)]` attribute.
+//
+// All completeable things support `#[rust_analyzer::do_not_complete(flyimport)]`,
+// which causes the thing to get excluded from flyimport completion. It will still
+// be completed when in scope. This is analogous to the setting `rust-analyzer.completion.autoimport.exclude`
+// with `"type": "always"`.
+//
+// In addition, traits support two more modes: `#[rust_analyzer::do_not_complete(methods_flyimport)]`,
+// which means the trait itself may still be flyimported but its methods won't, and
+// `#[rust_analyzer::do_not_complete(methods)]`, which means the methods won't be completed even when
+// the trait is in scope (but the trait itself may still be completed). The methods will still be completed
+// on `dyn Trait`, `impl Trait` or where the trait is specified in bounds. These modes correspond to
+// the settings `rust-analyzer.completion.autoimport.exclude` with `"type": "methods"` and
+// `rust-analyzer.completion.excludeTraits`, respectively.
+//
+// Malformed attributes will be ignored without warnings.
+//
+// Note that users have no way to override this attribute, so be careful and only include things
+// users definitely do not want to be completed!
+
+/// `#[rust_analyzer::do_not_complete(...)]` options.
+#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
+pub enum DoNotComplete {
+    /// No `#[rust_analyzer::do_not_complete(...)]`.
+    Complete,
+    /// `#[rust_analyzer::do_not_complete(flyimport)]`.
+    Flyimport,
+    /// `#[rust_analyzer::do_not_complete(methods_flyimport)]` (on a trait only).
+    MethodsFlyimport,
+    /// `#[rust_analyzer::do_not_complete(methods)]` (on a trait only).
+    Methods,
+}
path: crates/hir-def/src/lib.rs
user: login: ChayimFriedman2
body: Edited to that. But are you sure it needs to be `flyimport_ignore_methods` and not `ignore_flyimport_methods`?
created_at: 2025-03-27T02:56:19Z
author_association: CONTRIBUTOR
diff_hunk: @@ -1508,3 +1509,79 @@ pub struct UnresolvedMacro {
 
 #[derive(Default, Debug, Eq, PartialEq, Clone, Copy)]
 pub struct SyntheticSyntax;
+
+// Feature: Do Not Complete Attribute
+// Crate authors can opt their type out of completions in some cases.
+// This is done with the `#[rust_analyzer::do_not_complete(...)]` attribute.
+//
+// All completeable things support `#[rust_analyzer::do_not_complete(flyimport)]`,
+// which causes the thing to get excluded from flyimport completion. It will still
+// be completed when in scope. This is analogous to the setting `rust-analyzer.completion.autoimport.exclude`
+// with `"type": "always"`.
+//
+// In addition, traits support two more modes: `#[rust_analyzer::do_not_complete(methods_flyimport)]`,
+// which means the trait itself may still be flyimported but its methods won't, and
+// `#[rust_analyzer::do_not_complete(methods)]`, which means the methods won't be completed even when
+// the trait is in scope (but the trait itself may still be completed). The methods will still be completed
+// on `dyn Trait`, `impl Trait` or where the trait is specified in bounds. These modes correspond to
+// the settings `rust-analyzer.completion.autoimport.exclude` with `"type": "methods"` and
+// `rust-analyzer.completion.excludeTraits`, respectively.
+//
+// Malformed attributes will be ignored without warnings.
+//
+// Note that users have no way to override this attribute, so be careful and only include things
+// users definitely do not want to be completed!
+
+/// `#[rust_analyzer::do_not_complete(...)]` options.
+#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
+pub enum DoNotComplete {
+    /// No `#[rust_analyzer::do_not_complete(...)]`.
+    Complete,
+    /// `#[rust_analyzer::do_not_complete(flyimport)]`.
+    Flyimport,
+    /// `#[rust_analyzer::do_not_complete(methods_flyimport)]` (on a trait only).
+    MethodsFlyimport,
+    /// `#[rust_analyzer::do_not_complete(methods)]` (on a trait only).
+    Methods,
+}
path: crates/hir-def/src/lib.rs
user: login: Veykril
body: Oh I didn't even realize that I mixed that up. The latter seems more consistent I suppose so probably better to go with that?
created_at: 2025-03-27T06:15:40Z
author_association: MEMBER
diff_hunk: @@ -1508,3 +1509,79 @@ pub struct UnresolvedMacro {
 
 #[derive(Default, Debug, Eq, PartialEq, Clone, Copy)]
 pub struct SyntheticSyntax;
+
+// Feature: Do Not Complete Attribute
+// Crate authors can opt their type out of completions in some cases.
+// This is done with the `#[rust_analyzer::do_not_complete(...)]` attribute.
+//
+// All completeable things support `#[rust_analyzer::do_not_complete(flyimport)]`,
+// which causes the thing to get excluded from flyimport completion. It will still
+// be completed when in scope. This is analogous to the setting `rust-analyzer.completion.autoimport.exclude`
+// with `"type": "always"`.
+//
+// In addition, traits support two more modes: `#[rust_analyzer::do_not_complete(methods_flyimport)]`,
+// which means the trait itself may still be flyimported but its methods won't, and
+// `#[rust_analyzer::do_not_complete(methods)]`, which means the methods won't be completed even when
+// the trait is in scope (but the trait itself may still be completed). The methods will still be completed
+// on `dyn Trait`, `impl Trait` or where the trait is specified in bounds. These modes correspond to
+// the settings `rust-analyzer.completion.autoimport.exclude` with `"type": "methods"` and
+// `rust-analyzer.completion.excludeTraits`, respectively.
+//
+// Malformed attributes will be ignored without warnings.
+//
+// Note that users have no way to override this attribute, so be careful and only include things
+// users definitely do not want to be completed!
+
+/// `#[rust_analyzer::do_not_complete(...)]` options.
+#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
+pub enum DoNotComplete {
+    /// No `#[rust_analyzer::do_not_complete(...)]`.
+    Complete,
+    /// `#[rust_analyzer::do_not_complete(flyimport)]`.
+    Flyimport,
+    /// `#[rust_analyzer::do_not_complete(methods_flyimport)]` (on a trait only).
+    MethodsFlyimport,
+    /// `#[rust_analyzer::do_not_complete(methods)]` (on a trait only).
+    Methods,
+}
path: crates/hir-def/src/lib.rs
user: login: ChayimFriedman2
body: Edited.
created_at: 2025-03-27T12:01:32Z
author_association: CONTRIBUTOR

PR comments:
user: login: ChayimFriedman2
created_at: 2025-03-16T19:10:59Z
body: > Given this only affects flyimport (with the exception of traits), I feel like this would be more fitting to be recorded as a flag in the `ImportMap` `ImportInfo` entries and then either be filtered immediately in `search_for_imports` or have that flag propagate out of `search_for_imports`.

What about traits? Should they remain part of hir-def or should we lookup the attribute in `hir`?
author_association: CONTRIBUTOR
user: login: Veykril
created_at: 2025-03-16T19:17:46Z
body: We should probably lookup the attribute in `hir` 
author_association: MEMBER
user: login: ChayimFriedman2
created_at: 2025-03-16T20:53:57Z
body: I changed this to check the attribute in the import and symbols map. We can't filter it in `search_for_imports()` as other things are using it (e.g. qualify assist) that we don't want to filter that in.
author_association: CONTRIBUTOR
user: login: Veykril
created_at: 2025-03-17T14:40:05Z
body: Design wise, I wonder if we should go for something more generic like `rust_analyzer::completions(relevant stuff here)`?
author_association: MEMBER
user: login: ChayimFriedman2
created_at: 2025-03-17T17:56:08Z
body: Fixed the multi crates problem and removed legacy macros.
author_association: CONTRIBUTOR
user: login: davidbarsky
created_at: 2025-03-17T18:50:58Z
body: Maybe something more generic would be appropriate. Two questions:
1. How should `RustRover` handle this? Pretend to be rust-analyzer?
2. We already ignore `#[doc(hidden)]` items in completions, right?
author_association: CONTRIBUTOR
user: login: ChayimFriedman2
created_at: 2025-03-17T19:13:22Z
body: > 1. How should `RustRover` handle this? Pretend to be rust-analyzer?

I guess yes, if they want? We don't have a `#[rustrover::]` or an `#[ide::]` tool namespace, but we do have a `#[rust_analyzer::]` tool namespace.

> 2. We already ignore `#[doc(hidden)]` items in completions, right?

Yes, but e.g. `owo_colors` will not want to use that, because they do want the methods to show up in documentation. We need something more specific.
author_association: CONTRIBUTOR
user: login: ChayimFriedman2
created_at: 2025-03-20T07:20:27Z
body: I'm not objected to a more general attribute, but I also think we shouldn't hang this indefinitely on the perfect design choice. At worst, we can declare no stability.
author_association: CONTRIBUTOR
user: login: Veykril
created_at: 2025-03-28T09:06:32Z
body: Forgot to hit the button.
Either way nice, thanks!
author_association: MEMBER
user: login: joshka
created_at: 2025-04-02T01:25:05Z
body: Docs: https://rust-analyzer.github.io/book/features.html#completions-attribute

@ChayimFriedman2 It took a few steps to find this, perhaps would you mind adding it to the first comment?
author_association: CONTRIBUTOR