Unnamed repository; edit this file 'description' to name the repository.
Do not complete unstable items that use an internal feature
Unless the feature is enabled (and we're on nightly).
We can choose to not complete any unstable item if its feature is not enabled. However people may first type the thing then insert the feature. Only doing that for internal features is a good middle ground: normal people don't want their completion list polluted by `std::intrinsics` (even if they use nightly), and std people still get their completion if the feature is enabled (and realistically, it will be).
| -rw-r--r-- | crates/hir-def/src/attrs.rs | 35 | ||||
| -rw-r--r-- | crates/hir/src/attrs.rs | 13 | ||||
| -rw-r--r-- | crates/hir/src/lib.rs | 4 | ||||
| -rw-r--r-- | crates/ide-completion/src/context.rs | 54 | ||||
| -rw-r--r-- | crates/ide-completion/src/tests/expression.rs | 108 | ||||
| -rw-r--r-- | crates/intern/src/symbol/symbols.rs | 29 |
6 files changed, 238 insertions, 5 deletions
diff --git a/crates/hir-def/src/attrs.rs b/crates/hir-def/src/attrs.rs index b560d08492..9da5b98d83 100644 --- a/crates/hir-def/src/attrs.rs +++ b/crates/hir-def/src/attrs.rs @@ -1076,6 +1076,41 @@ impl AttrFlags { }) } } + + pub fn unstable_feature(self, db: &dyn DefDatabase, owner: AttrDefId) -> Option<Symbol> { + if !self.contains(AttrFlags::IS_UNSTABLE) { + return None; + } + + return unstable_feature(db, owner); + + #[salsa::tracked] + fn unstable_feature(db: &dyn DefDatabase, owner: AttrDefId) -> Option<Symbol> { + collect_attrs(db, owner, |attr| { + if let ast::Meta::TokenTreeMeta(attr) = attr + && let path = attr.path() + && path.is1("unstable") + && let Some(tt) = attr.token_tree() + { + let mut tt = TokenTreeChildren::new(&tt); + // Technically the `feature = "..."` always comes first, but it's not a requirement. + while let Some(token) = tt.next() { + if let NodeOrToken::Token(token) = token + && token.text() == "feature" + && let Some(NodeOrToken::Token(eq)) = tt.next() + && eq.kind() == T![=] + && let Some(NodeOrToken::Token(feature)) = tt.next() + && let Some(feature) = ast::String::cast(feature) + && let Ok(feature) = feature.value() + { + return ControlFlow::Break(Symbol::intern(&feature)); + } + } + } + ControlFlow::Continue(()) + }) + } + } } fn merge_repr(this: &mut ReprOptions, other: ReprOptions) { diff --git a/crates/hir/src/attrs.rs b/crates/hir/src/attrs.rs index 27e7985146..bec91032b9 100644 --- a/crates/hir/src/attrs.rs +++ b/crates/hir/src/attrs.rs @@ -85,6 +85,19 @@ impl AttrsWithOwner { self.attrs.contains(AttrFlags::IS_UNSTABLE) } + /// Currently, it could be that `is_unstable() == true` but `unstable_feature == None` + /// (due to unstable features not being retrieved for fields etc.). + #[inline] + pub fn unstable_feature(&self, db: &dyn HirDatabase) -> Option<Symbol> { + match self.owner { + AttrsOwner::AttrDef(owner) => self.attrs.unstable_feature(db, owner), + AttrsOwner::Field(_) + | AttrsOwner::LifetimeParam(_) + | AttrsOwner::TypeOrConstParam(_) + | AttrsOwner::Dummy => None, + } + } + #[inline] pub fn is_macro_export(&self) -> bool { self.attrs.contains(AttrFlags::IS_MACRO_EXPORT) diff --git a/crates/hir/src/lib.rs b/crates/hir/src/lib.rs index 7a4085c474..7664d07f0c 100644 --- a/crates/hir/src/lib.rs +++ b/crates/hir/src/lib.rs @@ -342,6 +342,10 @@ impl Crate { }) .map(Crate::from) } + + pub fn is_unstable_feature_enabled(self, db: &dyn HirDatabase, feature: &Symbol) -> bool { + crate_def_map(db, self.id).is_unstable_feature_enabled(feature) + } } #[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] diff --git a/crates/ide-completion/src/context.rs b/crates/ide-completion/src/context.rs index a9f9f7997c..b9520e9132 100644 --- a/crates/ide-completion/src/context.rs +++ b/crates/ide-completion/src/context.rs @@ -4,12 +4,12 @@ mod analysis; #[cfg(test)] mod tests; -use std::iter; +use std::{iter, sync::LazyLock}; use base_db::toolchain_channel; use hir::{ DisplayTarget, HasAttrs, InFile, Local, ModuleDef, ModuleSource, Name, PathResolution, - ScopeDef, Semantics, SemanticsScope, Symbol, Type, TypeInfo, + ScopeDef, Semantics, SemanticsScope, Symbol, Type, TypeInfo, sym, }; use ide_db::{ FilePosition, FxHashMap, FxHashSet, RootDatabase, famous_defs::FamousDefs, @@ -601,7 +601,18 @@ impl CompletionContext<'_> { let Some(attrs) = attrs else { return true; }; - !attrs.is_unstable() || self.is_nightly + if !attrs.is_unstable() { + return true; + } + if !self.is_nightly { + return false; + } + // Unstable on nightly, but we still don't want to suggest internal features, unless the feature flag is enabled. + let Some(unstable_feature) = attrs.unstable_feature(self.db) else { + return true; + }; + !INTERNAL_FEATURES.contains(&unstable_feature) + || self.krate.is_unstable_feature_enabled(self.db, &unstable_feature) } pub(crate) fn check_stability_and_hidden<I>(&self, item: I) -> bool @@ -924,3 +935,40 @@ const OP_TRAIT_LANG: &[hir::LangItem] = &[ hir::LangItem::Shr, hir::LangItem::Sub, ]; + +// FIXME: Find a way to keep this up to date somehow? +const INTERNAL_FEATURES_LIST: &[Symbol] = &[ + sym::abi_unadjusted, + sym::allocator_internals, + sym::allow_internal_unsafe, + sym::allow_internal_unstable, + sym::cfg_emscripten_wasm_eh, + sym::cfg_target_has_reliable_f16_f128, + sym::compiler_builtins, + sym::custom_mir, + sym::eii_internals, + sym::field_representing_type_raw, + sym::intrinsics, + sym::lang_items, + sym::link_cfg, + sym::more_maybe_bounds, + sym::negative_bounds, + sym::pattern_complexity_limit, + sym::prelude_import, + sym::profiler_runtime, + sym::rustc_attrs, + sym::staged_api, + sym::test_unstable_lint, + sym::builtin_syntax, + sym::link_llvm_intrinsics, + sym::needs_panic_runtime, + sym::panic_runtime, + sym::pattern_types, + sym::rustdoc_internals, + sym::contracts_internals, + sym::freeze_impls, + sym::unsized_fn_params, +]; + +static INTERNAL_FEATURES: LazyLock<FxHashSet<Symbol>> = + LazyLock::new(|| INTERNAL_FEATURES_LIST.iter().cloned().collect()); diff --git a/crates/ide-completion/src/tests/expression.rs b/crates/ide-completion/src/tests/expression.rs index 4a5983097a..294434297e 100644 --- a/crates/ide-completion/src/tests/expression.rs +++ b/crates/ide-completion/src/tests/expression.rs @@ -2273,7 +2273,7 @@ fn main() { $0 } //- /std.rs crate:std -#[unstable] +#[unstable(feature = "some_non_internal_feature")] pub struct UnstableButWeAreOnNightlyAnyway; "#, expect![[r#" @@ -2318,6 +2318,112 @@ pub struct UnstableButWeAreOnNightlyAnyway; } #[test] +fn expr_unstable_item_internal_feature() { + check( + r#" +//- toolchain:nightly +//- /main.rs crate:main deps:std +use std::*; +fn main() { + $0 +} +//- /std.rs crate:std +#[unstable(feature = "intrinsics")] +pub mod intrinsics {} + "#, + expect![[r#" + fn main() fn() + md std + bt u32 u32 + kw async + kw const + kw crate:: + kw enum + kw extern + kw false + kw fn + kw for + kw if + kw if let + kw impl + kw impl for + kw let + kw letm + kw loop + kw match + kw mod + kw return + kw self:: + kw static + kw struct + kw trait + kw true + kw type + kw union + kw unsafe + kw use + kw while + kw while let + sn macro_rules + sn pd + sn ppd + "#]], + ); + check( + r#" +//- toolchain:nightly +//- /main.rs crate:main deps:std +#![feature(intrinsics)] +use std::*; +fn main() { + $0 +} +//- /std.rs crate:std +#[unstable(feature = "intrinsics")] +pub mod intrinsics {} + "#, + expect![[r#" + fn main() fn() + md intrinsics + md std + bt u32 u32 + kw async + kw const + kw crate:: + kw enum + kw extern + kw false + kw fn + kw for + kw if + kw if let + kw impl + kw impl for + kw let + kw letm + kw loop + kw match + kw mod + kw return + kw self:: + kw static + kw struct + kw trait + kw true + kw type + kw union + kw unsafe + kw use + kw while + kw while let + sn macro_rules + sn pd + sn ppd + "#]], + ); +} + +#[test] fn inside_format_args_completions_work() { check( r#" diff --git a/crates/intern/src/symbol/symbols.rs b/crates/intern/src/symbol/symbols.rs index cc09a1aae7..4be1f79fb5 100644 --- a/crates/intern/src/symbol/symbols.rs +++ b/crates/intern/src/symbol/symbols.rs @@ -123,7 +123,6 @@ define_symbols! { all, alloc_layout, alloc, - allow_internal_unsafe, allow, any, as_str, @@ -541,4 +540,32 @@ define_symbols! { DispatchFromDyn, define_opaque, marker, + abi_unadjusted, + allocator_internals, + allow_internal_unsafe, + allow_internal_unstable, + cfg_emscripten_wasm_eh, + cfg_target_has_reliable_f16_f128, + compiler_builtins, + custom_mir, + eii_internals, + field_representing_type_raw, + intrinsics, + link_cfg, + more_maybe_bounds, + negative_bounds, + pattern_complexity_limit, + profiler_runtime, + rustc_attrs, + staged_api, + test_unstable_lint, + builtin_syntax, + link_llvm_intrinsics, + needs_panic_runtime, + panic_runtime, + pattern_types, + rustdoc_internals, + contracts_internals, + freeze_impls, + unsized_fn_params, } |