diff --git a/crates/hir-def/src/attr.rs b/crates/hir-def/src/attr.rs index 9224773aa8..cc59e78093 100644 --- a/crates/hir-def/src/attr.rs +++ b/crates/hir-def/src/attr.rs @@ -269,6 +269,10 @@ impl Attrs { pub fn is_proc_macro_derive(&self) -> bool { self.by_key("proc_macro_derive").exists() } + + pub fn is_unstable(&self) -> bool { + self.by_key("unstable").exists() + } } use std::slice::Iter as SliceIter; diff --git a/crates/ide-completion/src/completions.rs b/crates/ide-completion/src/completions.rs index b6a066f4f5..6f23bc5c74 100644 --- a/crates/ide-completion/src/completions.rs +++ b/crates/ide-completion/src/completions.rs @@ -23,7 +23,7 @@ pub(crate) mod env_vars; use std::iter; -use hir::{known, ScopeDef, Variant}; +use hir::{known, HasAttrs, ScopeDef, Variant}; use ide_db::{imports::import_assets::LocatedImport, SymbolKind}; use syntax::ast; @@ -181,6 +181,9 @@ impl Completions { resolution: hir::ScopeDef, doc_aliases: Vec, ) { + if !ctx.check_stability(resolution.attrs(ctx.db).as_deref()) { + return; + } let is_private_editable = match ctx.def_is_visible(&resolution) { Visible::Yes => false, Visible::Editable => true, @@ -206,6 +209,9 @@ impl Completions { local_name: hir::Name, resolution: hir::ScopeDef, ) { + if !ctx.check_stability(resolution.attrs(ctx.db).as_deref()) { + return; + } let is_private_editable = match ctx.def_is_visible(&resolution) { Visible::Yes => false, Visible::Editable => true, @@ -228,6 +234,9 @@ impl Completions { path_ctx: &PathCompletionCtx, e: hir::Enum, ) { + if !ctx.check_stability(Some(&e.attrs(ctx.db))) { + return; + } e.variants(ctx.db) .into_iter() .for_each(|variant| self.add_enum_variant(ctx, path_ctx, variant, None)); @@ -241,6 +250,9 @@ impl Completions { local_name: hir::Name, doc_aliases: Vec, ) { + if !ctx.check_stability(Some(&module.attrs(ctx.db))) { + return; + } self.add_path_resolution( ctx, path_ctx, @@ -257,6 +269,9 @@ impl Completions { mac: hir::Macro, local_name: hir::Name, ) { + if !ctx.check_stability(Some(&mac.attrs(ctx.db))) { + return; + } let is_private_editable = match ctx.is_visible(&mac) { Visible::Yes => false, Visible::Editable => true, @@ -280,6 +295,9 @@ impl Completions { func: hir::Function, local_name: Option, ) { + if !ctx.check_stability(Some(&func.attrs(ctx.db))) { + return; + } let is_private_editable = match ctx.is_visible(&func) { Visible::Yes => false, Visible::Editable => true, @@ -304,6 +322,9 @@ impl Completions { receiver: Option, local_name: Option, ) { + if !ctx.check_stability(Some(&func.attrs(ctx.db))) { + return; + } let is_private_editable = match ctx.is_visible(&func) { Visible::Yes => false, Visible::Editable => true, @@ -328,6 +349,9 @@ impl Completions { func: hir::Function, import: LocatedImport, ) { + if !ctx.check_stability(Some(&func.attrs(ctx.db))) { + return; + } let is_private_editable = match ctx.is_visible(&func) { Visible::Yes => false, Visible::Editable => true, @@ -348,6 +372,9 @@ impl Completions { } pub(crate) fn add_const(&mut self, ctx: &CompletionContext<'_>, konst: hir::Const) { + if !ctx.check_stability(Some(&konst.attrs(ctx.db))) { + return; + } let is_private_editable = match ctx.is_visible(&konst) { Visible::Yes => false, Visible::Editable => true, @@ -364,6 +391,9 @@ impl Completions { ctx: &CompletionContext<'_>, type_alias: hir::TypeAlias, ) { + if !ctx.check_stability(Some(&type_alias.attrs(ctx.db))) { + return; + } let is_private_editable = match ctx.is_visible(&type_alias) { Visible::Yes => false, Visible::Editable => true, @@ -380,6 +410,9 @@ impl Completions { ctx: &CompletionContext<'_>, type_alias: hir::TypeAlias, ) { + if !ctx.check_stability(Some(&type_alias.attrs(ctx.db))) { + return; + } self.add_opt(render_type_alias_with_eq(RenderContext::new(ctx), type_alias)); } @@ -390,6 +423,9 @@ impl Completions { variant: hir::Variant, path: hir::ModPath, ) { + if !ctx.check_stability(Some(&variant.attrs(ctx.db))) { + return; + } if let Some(builder) = render_variant_lit(RenderContext::new(ctx), path_ctx, None, variant, Some(path)) { @@ -404,6 +440,9 @@ impl Completions { variant: hir::Variant, local_name: Option, ) { + if !ctx.check_stability(Some(&variant.attrs(ctx.db))) { + return; + } if let PathCompletionCtx { kind: PathKind::Pat { pat_ctx }, .. } = path_ctx { cov_mark::hit!(enum_variant_pattern_path); self.add_variant_pat(ctx, pat_ctx, Some(path_ctx), variant, local_name); @@ -425,6 +464,9 @@ impl Completions { field: hir::Field, ty: &hir::Type, ) { + if !ctx.check_stability(Some(&field.attrs(ctx.db))) { + return; + } let is_private_editable = match ctx.is_visible(&field) { Visible::Yes => false, Visible::Editable => true, @@ -448,6 +490,9 @@ impl Completions { path: Option, local_name: Option, ) { + if !ctx.check_stability(Some(&strukt.attrs(ctx.db))) { + return; + } if let Some(builder) = render_struct_literal(RenderContext::new(ctx), path_ctx, strukt, path, local_name) { @@ -462,6 +507,9 @@ impl Completions { path: Option, local_name: Option, ) { + if !ctx.check_stability(Some(&un.attrs(ctx.db))) { + return; + } let item = render_union_literal(RenderContext::new(ctx), un, path, local_name); self.add_opt(item); } @@ -473,6 +521,8 @@ impl Completions { field: usize, ty: &hir::Type, ) { + // Only used for (unnamed) tuples, whose all fields *are* stable. No need to check + // stability here. let item = render_tuple_field(RenderContext::new(ctx), receiver, field, ty); self.add(item); } @@ -494,6 +544,9 @@ impl Completions { variant: hir::Variant, local_name: Option, ) { + if !ctx.check_stability(Some(&variant.attrs(ctx.db))) { + return; + } self.add_opt(render_variant_pat( RenderContext::new(ctx), pattern_ctx, @@ -511,6 +564,9 @@ impl Completions { variant: hir::Variant, path: hir::ModPath, ) { + if !ctx.check_stability(Some(&variant.attrs(ctx.db))) { + return; + } let path = Some(&path); self.add_opt(render_variant_pat( RenderContext::new(ctx), @@ -529,6 +585,9 @@ impl Completions { strukt: hir::Struct, local_name: Option, ) { + if !ctx.check_stability(Some(&strukt.attrs(ctx.db))) { + return; + } self.add_opt(render_struct_pat(RenderContext::new(ctx), pattern_ctx, strukt, local_name)); } } diff --git a/crates/ide-completion/src/completions/flyimport.rs b/crates/ide-completion/src/completions/flyimport.rs index 0979f6a6df..32d3fb8c62 100644 --- a/crates/ide-completion/src/completions/flyimport.rs +++ b/crates/ide-completion/src/completions/flyimport.rs @@ -267,8 +267,10 @@ fn import_on_the_fly( .into_iter() .filter(ns_filter) .filter(|import| { - !ctx.is_item_hidden(&import.item_to_import) - && !ctx.is_item_hidden(&import.original_item) + let item = &import.item_to_import; + !ctx.is_item_hidden(item) + && !ctx.is_item_hidden(item) + && ctx.check_stability(item.attrs(ctx.db).as_deref()) }) .sorted_by_key(|located_import| { compute_fuzzy_completion_order_key( @@ -315,8 +317,10 @@ fn import_on_the_fly_pat_( .into_iter() .filter(ns_filter) .filter(|import| { - !ctx.is_item_hidden(&import.item_to_import) - && !ctx.is_item_hidden(&import.original_item) + let item = &import.item_to_import; + !ctx.is_item_hidden(item) + && !ctx.is_item_hidden(item) + && ctx.check_stability(item.attrs(ctx.db).as_deref()) }) .sorted_by_key(|located_import| { compute_fuzzy_completion_order_key( diff --git a/crates/ide-completion/src/completions/item_list/trait_impl.rs b/crates/ide-completion/src/completions/item_list/trait_impl.rs index 889d90095f..e82908a361 100644 --- a/crates/ide-completion/src/completions/item_list/trait_impl.rs +++ b/crates/ide-completion/src/completions/item_list/trait_impl.rs @@ -150,21 +150,24 @@ fn complete_trait_impl( impl_def: &ast::Impl, ) { if let Some(hir_impl) = ctx.sema.to_def(impl_def) { - get_missing_assoc_items(&ctx.sema, impl_def).into_iter().for_each(|item| { - use self::ImplCompletionKind::*; - match (item, kind) { - (hir::AssocItem::Function(func), All | Fn) => { - add_function_impl(acc, ctx, replacement_range, func, hir_impl) + get_missing_assoc_items(&ctx.sema, impl_def) + .into_iter() + .filter(|item| ctx.check_stability(Some(&item.attrs(ctx.db)))) + .for_each(|item| { + use self::ImplCompletionKind::*; + match (item, kind) { + (hir::AssocItem::Function(func), All | Fn) => { + add_function_impl(acc, ctx, replacement_range, func, hir_impl) + } + (hir::AssocItem::TypeAlias(type_alias), All | TypeAlias) => { + add_type_alias_impl(acc, ctx, replacement_range, type_alias, hir_impl) + } + (hir::AssocItem::Const(const_), All | Const) => { + add_const_impl(acc, ctx, replacement_range, const_, hir_impl) + } + _ => {} } - (hir::AssocItem::TypeAlias(type_alias), All | TypeAlias) => { - add_type_alias_impl(acc, ctx, replacement_range, type_alias, hir_impl) - } - (hir::AssocItem::Const(const_), All | Const) => { - add_const_impl(acc, ctx, replacement_range, const_, hir_impl) - } - _ => {} - } - }); + }); } } diff --git a/crates/ide-completion/src/completions/use_.rs b/crates/ide-completion/src/completions/use_.rs index 546a1f4c49..45be4fb205 100644 --- a/crates/ide-completion/src/completions/use_.rs +++ b/crates/ide-completion/src/completions/use_.rs @@ -52,6 +52,9 @@ pub(crate) fn complete_use_path( ) }; for (name, def) in module_scope { + if !ctx.check_stability(def.attrs(ctx.db).as_deref()) { + continue; + } let is_name_already_imported = name .as_text() .map_or(false, |text| already_imported_names.contains(text.as_str())); diff --git a/crates/ide-completion/src/context.rs b/crates/ide-completion/src/context.rs index f6478d2ceb..d99414fe35 100644 --- a/crates/ide-completion/src/context.rs +++ b/crates/ide-completion/src/context.rs @@ -367,6 +367,8 @@ pub(crate) struct CompletionContext<'a> { pub(super) krate: hir::Crate, /// The module of the `scope`. pub(super) module: hir::Module, + /// Whether nightly toolchain is used. Cached since this is looked up a lot. + is_nightly: bool, /// The expected name of what we are completing. /// This is usually the parameter name of the function argument we are completing. @@ -386,7 +388,7 @@ pub(crate) struct CompletionContext<'a> { pub(super) depth_from_crate_root: usize, } -impl<'a> CompletionContext<'a> { +impl CompletionContext<'_> { /// The range of the identifier that is being completed. pub(crate) fn source_range(&self) -> TextRange { let kind = self.original_token.kind(); @@ -451,6 +453,12 @@ impl<'a> CompletionContext<'a> { } } + /// Checks whether this item should be listed in regards to stability. Returns `true` if we should. + pub(crate) fn check_stability(&self, attrs: Option<&hir::Attrs>) -> bool { + let Some(attrs) = attrs else { return true; }; + !attrs.is_unstable() || self.is_nightly + } + /// Whether the given trait is an operator trait or not. pub(crate) fn is_ops_trait(&self, trait_: hir::Trait) -> bool { match trait_.attrs(self.db).lang() { @@ -624,6 +632,11 @@ impl<'a> CompletionContext<'a> { let krate = scope.krate(); let module = scope.module(); + let toolchain = db.crate_graph()[krate.into()].channel; + // `toolchain == None` means we're in some detached files. Since we have no information on + // the toolchain being used, let's just allow unstable items to be listed. + let is_nightly = matches!(toolchain, Some(base_db::ReleaseChannel::Nightly) | None); + let mut locals = FxHashMap::default(); scope.process_all_names(&mut |name, scope| { if let ScopeDef::Local(local) = scope { @@ -643,6 +656,7 @@ impl<'a> CompletionContext<'a> { token, krate, module, + is_nightly, expected_name, expected_type, qualifier_ctx,