Merge pull request #22044 from ChayimFriedman2/internal-features

feat: Do not complete unstable items that use an internal feature
This commit is contained in:
Lukas Wirth
2026-04-15 13:42:25 +00:00
committed by GitHub
6 changed files with 238 additions and 5 deletions
@@ -1076,6 +1076,41 @@ fn derive_info(db: &dyn DefDatabase, owner: MacroId) -> Option<DeriveInfo> {
})
}
}
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) {
@@ -85,6 +85,19 @@ pub fn is_unstable(&self) -> bool {
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)
@@ -342,6 +342,10 @@ fn core(db: &dyn HirDatabase) -> Option<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)]
@@ -4,12 +4,12 @@
#[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 @@ pub(crate) fn check_stability(&self, attrs: Option<&hir::AttrsWithOwner>) -> boo
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 @@ pub(crate) fn new(
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());
@@ -2273,7 +2273,7 @@ fn main() {
$0
}
//- /std.rs crate:std
#[unstable]
#[unstable(feature = "some_non_internal_feature")]
pub struct UnstableButWeAreOnNightlyAnyway;
"#,
expect![[r#"
@@ -2317,6 +2317,112 @@ fn main() fn()
);
}
#[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(
@@ -123,7 +123,6 @@ pub(super) fn prefill() -> DashMap<Symbol, (), BuildHasherDefault<FxHasher>> {
all,
alloc_layout,
alloc,
allow_internal_unsafe,
allow,
any,
as_str,
@@ -541,4 +540,32 @@ pub(super) fn prefill() -> DashMap<Symbol, (), BuildHasherDefault<FxHasher>> {
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,
}