Auto merge of #14549 - lowr:patch/no-unstable-item-compl-on-stable, r=Veykril
Don't suggest unstable items on stable toolchain Closes #3020 This PR implements stability check in `ide-completion` so that unstable items are only suggested if you're on nightly toolchain. It's a bit unfortunate `CompletionContext::check_stability()` is spammed all over the crate, but we should call it before building `CompletionItem` as you cannot get attributes on the item it's completing from that struct. I looked up every callsite of `Builder::add_to()`, `Completions::add[_opt]()`, and`Completions::add_all()` and inserted the check wherever necessary. The tests are admittedly incomplete in that I didn't add tests for every kind of item as I thought that would be too big and not worthwhile. I copy-pasted some existing basic tests in every test module and adjusted them.
This commit is contained in:
commit
fd276218ec
@ -4,7 +4,8 @@
|
|||||||
use cfg::CfgOptions;
|
use cfg::CfgOptions;
|
||||||
use rustc_hash::FxHashMap;
|
use rustc_hash::FxHashMap;
|
||||||
use test_utils::{
|
use test_utils::{
|
||||||
extract_range_or_offset, Fixture, RangeOrOffset, CURSOR_MARKER, ESCAPED_CURSOR_MARKER,
|
extract_range_or_offset, Fixture, FixtureWithProjectMeta, RangeOrOffset, CURSOR_MARKER,
|
||||||
|
ESCAPED_CURSOR_MARKER,
|
||||||
};
|
};
|
||||||
use tt::token_id::{Leaf, Subtree, TokenTree};
|
use tt::token_id::{Leaf, Subtree, TokenTree};
|
||||||
use vfs::{file_set::FileSet, VfsPath};
|
use vfs::{file_set::FileSet, VfsPath};
|
||||||
@ -12,7 +13,7 @@
|
|||||||
use crate::{
|
use crate::{
|
||||||
input::{CrateName, CrateOrigin, LangCrateOrigin},
|
input::{CrateName, CrateOrigin, LangCrateOrigin},
|
||||||
Change, CrateDisplayName, CrateGraph, CrateId, Dependency, Edition, Env, FileId, FilePosition,
|
Change, CrateDisplayName, CrateGraph, CrateId, Dependency, Edition, Env, FileId, FilePosition,
|
||||||
FileRange, ProcMacro, ProcMacroExpander, ProcMacroExpansionError, ProcMacros,
|
FileRange, ProcMacro, ProcMacroExpander, ProcMacroExpansionError, ProcMacros, ReleaseChannel,
|
||||||
SourceDatabaseExt, SourceRoot, SourceRootId,
|
SourceDatabaseExt, SourceRoot, SourceRootId,
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -102,7 +103,14 @@ pub fn parse_with_proc_macros(
|
|||||||
ra_fixture: &str,
|
ra_fixture: &str,
|
||||||
mut proc_macro_defs: Vec<(String, ProcMacro)>,
|
mut proc_macro_defs: Vec<(String, ProcMacro)>,
|
||||||
) -> ChangeFixture {
|
) -> ChangeFixture {
|
||||||
let (mini_core, proc_macro_names, fixture) = Fixture::parse(ra_fixture);
|
let FixtureWithProjectMeta { fixture, mini_core, proc_macro_names, toolchain } =
|
||||||
|
FixtureWithProjectMeta::parse(ra_fixture);
|
||||||
|
let toolchain = toolchain
|
||||||
|
.map(|it| {
|
||||||
|
ReleaseChannel::from_str(&it)
|
||||||
|
.unwrap_or_else(|| panic!("unknown release channel found: {it}"))
|
||||||
|
})
|
||||||
|
.unwrap_or(ReleaseChannel::Stable);
|
||||||
let mut change = Change::new();
|
let mut change = Change::new();
|
||||||
|
|
||||||
let mut files = Vec::new();
|
let mut files = Vec::new();
|
||||||
@ -166,7 +174,7 @@ pub fn parse_with_proc_macros(
|
|||||||
.as_deref()
|
.as_deref()
|
||||||
.map(Arc::from)
|
.map(Arc::from)
|
||||||
.ok_or_else(|| "target_data_layout unset".into()),
|
.ok_or_else(|| "target_data_layout unset".into()),
|
||||||
None,
|
Some(toolchain),
|
||||||
);
|
);
|
||||||
let prev = crates.insert(crate_name.clone(), crate_id);
|
let prev = crates.insert(crate_name.clone(), crate_id);
|
||||||
assert!(prev.is_none());
|
assert!(prev.is_none());
|
||||||
@ -205,7 +213,7 @@ pub fn parse_with_proc_macros(
|
|||||||
default_target_data_layout
|
default_target_data_layout
|
||||||
.map(|x| x.into())
|
.map(|x| x.into())
|
||||||
.ok_or_else(|| "target_data_layout unset".into()),
|
.ok_or_else(|| "target_data_layout unset".into()),
|
||||||
None,
|
Some(toolchain),
|
||||||
);
|
);
|
||||||
} else {
|
} else {
|
||||||
for (from, to, prelude) in crate_deps {
|
for (from, to, prelude) in crate_deps {
|
||||||
@ -247,7 +255,7 @@ pub fn parse_with_proc_macros(
|
|||||||
false,
|
false,
|
||||||
CrateOrigin::Lang(LangCrateOrigin::Core),
|
CrateOrigin::Lang(LangCrateOrigin::Core),
|
||||||
target_layout.clone(),
|
target_layout.clone(),
|
||||||
None,
|
Some(toolchain),
|
||||||
);
|
);
|
||||||
|
|
||||||
for krate in all_crates {
|
for krate in all_crates {
|
||||||
@ -286,7 +294,7 @@ pub fn parse_with_proc_macros(
|
|||||||
true,
|
true,
|
||||||
CrateOrigin::Local { repo: None, name: None },
|
CrateOrigin::Local { repo: None, name: None },
|
||||||
target_layout,
|
target_layout,
|
||||||
None,
|
Some(toolchain),
|
||||||
);
|
);
|
||||||
proc_macros.insert(proc_macros_crate, Ok(proc_macro));
|
proc_macros.insert(proc_macros_crate, Ok(proc_macro));
|
||||||
|
|
||||||
|
@ -269,6 +269,10 @@ pub fn is_proc_macro_attribute(&self) -> bool {
|
|||||||
pub fn is_proc_macro_derive(&self) -> bool {
|
pub fn is_proc_macro_derive(&self) -> bool {
|
||||||
self.by_key("proc_macro_derive").exists()
|
self.by_key("proc_macro_derive").exists()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn is_unstable(&self) -> bool {
|
||||||
|
self.by_key("unstable").exists()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
use std::slice::Iter as SliceIter;
|
use std::slice::Iter as SliceIter;
|
||||||
|
@ -23,7 +23,7 @@
|
|||||||
|
|
||||||
use std::iter;
|
use std::iter;
|
||||||
|
|
||||||
use hir::{known, ScopeDef, Variant};
|
use hir::{known, HasAttrs, ScopeDef, Variant};
|
||||||
use ide_db::{imports::import_assets::LocatedImport, SymbolKind};
|
use ide_db::{imports::import_assets::LocatedImport, SymbolKind};
|
||||||
use syntax::ast;
|
use syntax::ast;
|
||||||
|
|
||||||
@ -181,6 +181,9 @@ pub(crate) fn add_path_resolution(
|
|||||||
resolution: hir::ScopeDef,
|
resolution: hir::ScopeDef,
|
||||||
doc_aliases: Vec<syntax::SmolStr>,
|
doc_aliases: Vec<syntax::SmolStr>,
|
||||||
) {
|
) {
|
||||||
|
if !ctx.check_stability(resolution.attrs(ctx.db).as_deref()) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
let is_private_editable = match ctx.def_is_visible(&resolution) {
|
let is_private_editable = match ctx.def_is_visible(&resolution) {
|
||||||
Visible::Yes => false,
|
Visible::Yes => false,
|
||||||
Visible::Editable => true,
|
Visible::Editable => true,
|
||||||
@ -206,6 +209,9 @@ pub(crate) fn add_pattern_resolution(
|
|||||||
local_name: hir::Name,
|
local_name: hir::Name,
|
||||||
resolution: hir::ScopeDef,
|
resolution: hir::ScopeDef,
|
||||||
) {
|
) {
|
||||||
|
if !ctx.check_stability(resolution.attrs(ctx.db).as_deref()) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
let is_private_editable = match ctx.def_is_visible(&resolution) {
|
let is_private_editable = match ctx.def_is_visible(&resolution) {
|
||||||
Visible::Yes => false,
|
Visible::Yes => false,
|
||||||
Visible::Editable => true,
|
Visible::Editable => true,
|
||||||
@ -228,6 +234,9 @@ pub(crate) fn add_enum_variants(
|
|||||||
path_ctx: &PathCompletionCtx,
|
path_ctx: &PathCompletionCtx,
|
||||||
e: hir::Enum,
|
e: hir::Enum,
|
||||||
) {
|
) {
|
||||||
|
if !ctx.check_stability(Some(&e.attrs(ctx.db))) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
e.variants(ctx.db)
|
e.variants(ctx.db)
|
||||||
.into_iter()
|
.into_iter()
|
||||||
.for_each(|variant| self.add_enum_variant(ctx, path_ctx, variant, None));
|
.for_each(|variant| self.add_enum_variant(ctx, path_ctx, variant, None));
|
||||||
@ -241,6 +250,9 @@ pub(crate) fn add_module(
|
|||||||
local_name: hir::Name,
|
local_name: hir::Name,
|
||||||
doc_aliases: Vec<syntax::SmolStr>,
|
doc_aliases: Vec<syntax::SmolStr>,
|
||||||
) {
|
) {
|
||||||
|
if !ctx.check_stability(Some(&module.attrs(ctx.db))) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
self.add_path_resolution(
|
self.add_path_resolution(
|
||||||
ctx,
|
ctx,
|
||||||
path_ctx,
|
path_ctx,
|
||||||
@ -257,6 +269,9 @@ pub(crate) fn add_macro(
|
|||||||
mac: hir::Macro,
|
mac: hir::Macro,
|
||||||
local_name: hir::Name,
|
local_name: hir::Name,
|
||||||
) {
|
) {
|
||||||
|
if !ctx.check_stability(Some(&mac.attrs(ctx.db))) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
let is_private_editable = match ctx.is_visible(&mac) {
|
let is_private_editable = match ctx.is_visible(&mac) {
|
||||||
Visible::Yes => false,
|
Visible::Yes => false,
|
||||||
Visible::Editable => true,
|
Visible::Editable => true,
|
||||||
@ -280,6 +295,9 @@ pub(crate) fn add_function(
|
|||||||
func: hir::Function,
|
func: hir::Function,
|
||||||
local_name: Option<hir::Name>,
|
local_name: Option<hir::Name>,
|
||||||
) {
|
) {
|
||||||
|
if !ctx.check_stability(Some(&func.attrs(ctx.db))) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
let is_private_editable = match ctx.is_visible(&func) {
|
let is_private_editable = match ctx.is_visible(&func) {
|
||||||
Visible::Yes => false,
|
Visible::Yes => false,
|
||||||
Visible::Editable => true,
|
Visible::Editable => true,
|
||||||
@ -304,6 +322,9 @@ pub(crate) fn add_method(
|
|||||||
receiver: Option<hir::Name>,
|
receiver: Option<hir::Name>,
|
||||||
local_name: Option<hir::Name>,
|
local_name: Option<hir::Name>,
|
||||||
) {
|
) {
|
||||||
|
if !ctx.check_stability(Some(&func.attrs(ctx.db))) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
let is_private_editable = match ctx.is_visible(&func) {
|
let is_private_editable = match ctx.is_visible(&func) {
|
||||||
Visible::Yes => false,
|
Visible::Yes => false,
|
||||||
Visible::Editable => true,
|
Visible::Editable => true,
|
||||||
@ -328,6 +349,9 @@ pub(crate) fn add_method_with_import(
|
|||||||
func: hir::Function,
|
func: hir::Function,
|
||||||
import: LocatedImport,
|
import: LocatedImport,
|
||||||
) {
|
) {
|
||||||
|
if !ctx.check_stability(Some(&func.attrs(ctx.db))) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
let is_private_editable = match ctx.is_visible(&func) {
|
let is_private_editable = match ctx.is_visible(&func) {
|
||||||
Visible::Yes => false,
|
Visible::Yes => false,
|
||||||
Visible::Editable => true,
|
Visible::Editable => true,
|
||||||
@ -348,6 +372,9 @@ pub(crate) fn add_method_with_import(
|
|||||||
}
|
}
|
||||||
|
|
||||||
pub(crate) fn add_const(&mut self, ctx: &CompletionContext<'_>, konst: hir::Const) {
|
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) {
|
let is_private_editable = match ctx.is_visible(&konst) {
|
||||||
Visible::Yes => false,
|
Visible::Yes => false,
|
||||||
Visible::Editable => true,
|
Visible::Editable => true,
|
||||||
@ -364,6 +391,9 @@ pub(crate) fn add_type_alias(
|
|||||||
ctx: &CompletionContext<'_>,
|
ctx: &CompletionContext<'_>,
|
||||||
type_alias: hir::TypeAlias,
|
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) {
|
let is_private_editable = match ctx.is_visible(&type_alias) {
|
||||||
Visible::Yes => false,
|
Visible::Yes => false,
|
||||||
Visible::Editable => true,
|
Visible::Editable => true,
|
||||||
@ -380,6 +410,9 @@ pub(crate) fn add_type_alias_with_eq(
|
|||||||
ctx: &CompletionContext<'_>,
|
ctx: &CompletionContext<'_>,
|
||||||
type_alias: hir::TypeAlias,
|
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));
|
self.add_opt(render_type_alias_with_eq(RenderContext::new(ctx), type_alias));
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -390,6 +423,9 @@ pub(crate) fn add_qualified_enum_variant(
|
|||||||
variant: hir::Variant,
|
variant: hir::Variant,
|
||||||
path: hir::ModPath,
|
path: hir::ModPath,
|
||||||
) {
|
) {
|
||||||
|
if !ctx.check_stability(Some(&variant.attrs(ctx.db))) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
if let Some(builder) =
|
if let Some(builder) =
|
||||||
render_variant_lit(RenderContext::new(ctx), path_ctx, None, variant, Some(path))
|
render_variant_lit(RenderContext::new(ctx), path_ctx, None, variant, Some(path))
|
||||||
{
|
{
|
||||||
@ -404,6 +440,9 @@ pub(crate) fn add_enum_variant(
|
|||||||
variant: hir::Variant,
|
variant: hir::Variant,
|
||||||
local_name: Option<hir::Name>,
|
local_name: Option<hir::Name>,
|
||||||
) {
|
) {
|
||||||
|
if !ctx.check_stability(Some(&variant.attrs(ctx.db))) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
if let PathCompletionCtx { kind: PathKind::Pat { pat_ctx }, .. } = path_ctx {
|
if let PathCompletionCtx { kind: PathKind::Pat { pat_ctx }, .. } = path_ctx {
|
||||||
cov_mark::hit!(enum_variant_pattern_path);
|
cov_mark::hit!(enum_variant_pattern_path);
|
||||||
self.add_variant_pat(ctx, pat_ctx, Some(path_ctx), variant, local_name);
|
self.add_variant_pat(ctx, pat_ctx, Some(path_ctx), variant, local_name);
|
||||||
@ -425,6 +464,9 @@ pub(crate) fn add_field(
|
|||||||
field: hir::Field,
|
field: hir::Field,
|
||||||
ty: &hir::Type,
|
ty: &hir::Type,
|
||||||
) {
|
) {
|
||||||
|
if !ctx.check_stability(Some(&field.attrs(ctx.db))) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
let is_private_editable = match ctx.is_visible(&field) {
|
let is_private_editable = match ctx.is_visible(&field) {
|
||||||
Visible::Yes => false,
|
Visible::Yes => false,
|
||||||
Visible::Editable => true,
|
Visible::Editable => true,
|
||||||
@ -448,6 +490,9 @@ pub(crate) fn add_struct_literal(
|
|||||||
path: Option<hir::ModPath>,
|
path: Option<hir::ModPath>,
|
||||||
local_name: Option<hir::Name>,
|
local_name: Option<hir::Name>,
|
||||||
) {
|
) {
|
||||||
|
if !ctx.check_stability(Some(&strukt.attrs(ctx.db))) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
if let Some(builder) =
|
if let Some(builder) =
|
||||||
render_struct_literal(RenderContext::new(ctx), path_ctx, strukt, path, local_name)
|
render_struct_literal(RenderContext::new(ctx), path_ctx, strukt, path, local_name)
|
||||||
{
|
{
|
||||||
@ -462,6 +507,9 @@ pub(crate) fn add_union_literal(
|
|||||||
path: Option<hir::ModPath>,
|
path: Option<hir::ModPath>,
|
||||||
local_name: Option<hir::Name>,
|
local_name: Option<hir::Name>,
|
||||||
) {
|
) {
|
||||||
|
if !ctx.check_stability(Some(&un.attrs(ctx.db))) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
let item = render_union_literal(RenderContext::new(ctx), un, path, local_name);
|
let item = render_union_literal(RenderContext::new(ctx), un, path, local_name);
|
||||||
self.add_opt(item);
|
self.add_opt(item);
|
||||||
}
|
}
|
||||||
@ -473,6 +521,8 @@ pub(crate) fn add_tuple_field(
|
|||||||
field: usize,
|
field: usize,
|
||||||
ty: &hir::Type,
|
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);
|
let item = render_tuple_field(RenderContext::new(ctx), receiver, field, ty);
|
||||||
self.add(item);
|
self.add(item);
|
||||||
}
|
}
|
||||||
@ -494,6 +544,9 @@ pub(crate) fn add_variant_pat(
|
|||||||
variant: hir::Variant,
|
variant: hir::Variant,
|
||||||
local_name: Option<hir::Name>,
|
local_name: Option<hir::Name>,
|
||||||
) {
|
) {
|
||||||
|
if !ctx.check_stability(Some(&variant.attrs(ctx.db))) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
self.add_opt(render_variant_pat(
|
self.add_opt(render_variant_pat(
|
||||||
RenderContext::new(ctx),
|
RenderContext::new(ctx),
|
||||||
pattern_ctx,
|
pattern_ctx,
|
||||||
@ -511,6 +564,9 @@ pub(crate) fn add_qualified_variant_pat(
|
|||||||
variant: hir::Variant,
|
variant: hir::Variant,
|
||||||
path: hir::ModPath,
|
path: hir::ModPath,
|
||||||
) {
|
) {
|
||||||
|
if !ctx.check_stability(Some(&variant.attrs(ctx.db))) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
let path = Some(&path);
|
let path = Some(&path);
|
||||||
self.add_opt(render_variant_pat(
|
self.add_opt(render_variant_pat(
|
||||||
RenderContext::new(ctx),
|
RenderContext::new(ctx),
|
||||||
@ -529,6 +585,9 @@ pub(crate) fn add_struct_pat(
|
|||||||
strukt: hir::Struct,
|
strukt: hir::Struct,
|
||||||
local_name: Option<hir::Name>,
|
local_name: Option<hir::Name>,
|
||||||
) {
|
) {
|
||||||
|
if !ctx.check_stability(Some(&strukt.attrs(ctx.db))) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
self.add_opt(render_struct_pat(RenderContext::new(ctx), pattern_ctx, strukt, local_name));
|
self.add_opt(render_struct_pat(RenderContext::new(ctx), pattern_ctx, strukt, local_name));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -172,6 +172,43 @@ fn foo(s: S) { s.$0 }
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn no_unstable_method_on_stable() {
|
||||||
|
check(
|
||||||
|
r#"
|
||||||
|
//- /main.rs crate:main deps:std
|
||||||
|
fn foo(s: std::S) { s.$0 }
|
||||||
|
//- /std.rs crate:std
|
||||||
|
pub struct S;
|
||||||
|
impl S {
|
||||||
|
#[unstable]
|
||||||
|
pub fn bar(&self) {}
|
||||||
|
}
|
||||||
|
"#,
|
||||||
|
expect![""],
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn unstable_method_on_nightly() {
|
||||||
|
check(
|
||||||
|
r#"
|
||||||
|
//- toolchain:nightly
|
||||||
|
//- /main.rs crate:main deps:std
|
||||||
|
fn foo(s: std::S) { s.$0 }
|
||||||
|
//- /std.rs crate:std
|
||||||
|
pub struct S;
|
||||||
|
impl S {
|
||||||
|
#[unstable]
|
||||||
|
pub fn bar(&self) {}
|
||||||
|
}
|
||||||
|
"#,
|
||||||
|
expect![[r#"
|
||||||
|
me bar() fn(&self)
|
||||||
|
"#]],
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_struct_field_completion_self() {
|
fn test_struct_field_completion_self() {
|
||||||
check(
|
check(
|
||||||
|
@ -267,8 +267,10 @@ fn import_on_the_fly(
|
|||||||
.into_iter()
|
.into_iter()
|
||||||
.filter(ns_filter)
|
.filter(ns_filter)
|
||||||
.filter(|import| {
|
.filter(|import| {
|
||||||
!ctx.is_item_hidden(&import.item_to_import)
|
let item = &import.item_to_import;
|
||||||
&& !ctx.is_item_hidden(&import.original_item)
|
!ctx.is_item_hidden(item)
|
||||||
|
&& !ctx.is_item_hidden(item)
|
||||||
|
&& ctx.check_stability(item.attrs(ctx.db).as_deref())
|
||||||
})
|
})
|
||||||
.sorted_by_key(|located_import| {
|
.sorted_by_key(|located_import| {
|
||||||
compute_fuzzy_completion_order_key(
|
compute_fuzzy_completion_order_key(
|
||||||
@ -315,8 +317,10 @@ fn import_on_the_fly_pat_(
|
|||||||
.into_iter()
|
.into_iter()
|
||||||
.filter(ns_filter)
|
.filter(ns_filter)
|
||||||
.filter(|import| {
|
.filter(|import| {
|
||||||
!ctx.is_item_hidden(&import.item_to_import)
|
let item = &import.item_to_import;
|
||||||
&& !ctx.is_item_hidden(&import.original_item)
|
!ctx.is_item_hidden(item)
|
||||||
|
&& !ctx.is_item_hidden(item)
|
||||||
|
&& ctx.check_stability(item.attrs(ctx.db).as_deref())
|
||||||
})
|
})
|
||||||
.sorted_by_key(|located_import| {
|
.sorted_by_key(|located_import| {
|
||||||
compute_fuzzy_completion_order_key(
|
compute_fuzzy_completion_order_key(
|
||||||
|
@ -150,21 +150,24 @@ fn complete_trait_impl(
|
|||||||
impl_def: &ast::Impl,
|
impl_def: &ast::Impl,
|
||||||
) {
|
) {
|
||||||
if let Some(hir_impl) = ctx.sema.to_def(impl_def) {
|
if let Some(hir_impl) = ctx.sema.to_def(impl_def) {
|
||||||
get_missing_assoc_items(&ctx.sema, impl_def).into_iter().for_each(|item| {
|
get_missing_assoc_items(&ctx.sema, impl_def)
|
||||||
use self::ImplCompletionKind::*;
|
.into_iter()
|
||||||
match (item, kind) {
|
.filter(|item| ctx.check_stability(Some(&item.attrs(ctx.db))))
|
||||||
(hir::AssocItem::Function(func), All | Fn) => {
|
.for_each(|item| {
|
||||||
add_function_impl(acc, ctx, replacement_range, func, hir_impl)
|
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)
|
|
||||||
}
|
|
||||||
_ => {}
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -52,6 +52,9 @@ pub(crate) fn complete_use_path(
|
|||||||
)
|
)
|
||||||
};
|
};
|
||||||
for (name, def) in module_scope {
|
for (name, def) in module_scope {
|
||||||
|
if !ctx.check_stability(def.attrs(ctx.db).as_deref()) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
let is_name_already_imported = name
|
let is_name_already_imported = name
|
||||||
.as_text()
|
.as_text()
|
||||||
.map_or(false, |text| already_imported_names.contains(text.as_str()));
|
.map_or(false, |text| already_imported_names.contains(text.as_str()));
|
||||||
|
@ -367,6 +367,8 @@ pub(crate) struct CompletionContext<'a> {
|
|||||||
pub(super) krate: hir::Crate,
|
pub(super) krate: hir::Crate,
|
||||||
/// The module of the `scope`.
|
/// The module of the `scope`.
|
||||||
pub(super) module: hir::Module,
|
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.
|
/// The expected name of what we are completing.
|
||||||
/// This is usually the parameter name of the function argument 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,
|
pub(super) depth_from_crate_root: usize,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<'a> CompletionContext<'a> {
|
impl CompletionContext<'_> {
|
||||||
/// The range of the identifier that is being completed.
|
/// The range of the identifier that is being completed.
|
||||||
pub(crate) fn source_range(&self) -> TextRange {
|
pub(crate) fn source_range(&self) -> TextRange {
|
||||||
let kind = self.original_token.kind();
|
let kind = self.original_token.kind();
|
||||||
@ -451,6 +453,12 @@ pub(crate) fn is_item_hidden(&self, item: &hir::ItemInNs) -> bool {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// 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.
|
/// Whether the given trait is an operator trait or not.
|
||||||
pub(crate) fn is_ops_trait(&self, trait_: hir::Trait) -> bool {
|
pub(crate) fn is_ops_trait(&self, trait_: hir::Trait) -> bool {
|
||||||
match trait_.attrs(self.db).lang() {
|
match trait_.attrs(self.db).lang() {
|
||||||
@ -624,6 +632,11 @@ pub(super) fn new(
|
|||||||
let krate = scope.krate();
|
let krate = scope.krate();
|
||||||
let module = scope.module();
|
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();
|
let mut locals = FxHashMap::default();
|
||||||
scope.process_all_names(&mut |name, scope| {
|
scope.process_all_names(&mut |name, scope| {
|
||||||
if let ScopeDef::Local(local) = scope {
|
if let ScopeDef::Local(local) = scope {
|
||||||
@ -643,6 +656,7 @@ pub(super) fn new(
|
|||||||
token,
|
token,
|
||||||
krate,
|
krate,
|
||||||
module,
|
module,
|
||||||
|
is_nightly,
|
||||||
expected_name,
|
expected_name,
|
||||||
expected_type,
|
expected_type,
|
||||||
qualifier_ctx,
|
qualifier_ctx,
|
||||||
|
@ -23,6 +23,7 @@
|
|||||||
mod use_tree;
|
mod use_tree;
|
||||||
mod visibility;
|
mod visibility;
|
||||||
|
|
||||||
|
use expect_test::Expect;
|
||||||
use hir::PrefixKind;
|
use hir::PrefixKind;
|
||||||
use ide_db::{
|
use ide_db::{
|
||||||
base_db::{fixture::ChangeFixture, FileLoader, FilePosition},
|
base_db::{fixture::ChangeFixture, FileLoader, FilePosition},
|
||||||
@ -215,6 +216,11 @@ pub(crate) fn check_edit_with_config(
|
|||||||
assert_eq_text!(&ra_fixture_after, &actual)
|
assert_eq_text!(&ra_fixture_after, &actual)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn check_empty(ra_fixture: &str, expect: Expect) {
|
||||||
|
let actual = completion_list(ra_fixture);
|
||||||
|
expect.assert_eq(&actual);
|
||||||
|
}
|
||||||
|
|
||||||
pub(crate) fn get_all_items(
|
pub(crate) fn get_all_items(
|
||||||
config: CompletionConfig,
|
config: CompletionConfig,
|
||||||
code: &str,
|
code: &str,
|
||||||
|
@ -1,18 +1,13 @@
|
|||||||
//! Completion tests for expressions.
|
//! Completion tests for expressions.
|
||||||
use expect_test::{expect, Expect};
|
use expect_test::{expect, Expect};
|
||||||
|
|
||||||
use crate::tests::{check_edit, completion_list, BASE_ITEMS_FIXTURE};
|
use crate::tests::{check_edit, check_empty, completion_list, BASE_ITEMS_FIXTURE};
|
||||||
|
|
||||||
fn check(ra_fixture: &str, expect: Expect) {
|
fn check(ra_fixture: &str, expect: Expect) {
|
||||||
let actual = completion_list(&format!("{BASE_ITEMS_FIXTURE}{ra_fixture}"));
|
let actual = completion_list(&format!("{BASE_ITEMS_FIXTURE}{ra_fixture}"));
|
||||||
expect.assert_eq(&actual)
|
expect.assert_eq(&actual)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn check_empty(ra_fixture: &str, expect: Expect) {
|
|
||||||
let actual = completion_list(ra_fixture);
|
|
||||||
expect.assert_eq(&actual);
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn complete_literal_struct_with_a_private_field() {
|
fn complete_literal_struct_with_a_private_field() {
|
||||||
// `FooDesc.bar` is private, the completion should not be triggered.
|
// `FooDesc.bar` is private, the completion should not be triggered.
|
||||||
@ -997,3 +992,105 @@ fn foo() fn()
|
|||||||
"#]],
|
"#]],
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn expr_no_unstable_item_on_stable() {
|
||||||
|
check_empty(
|
||||||
|
r#"
|
||||||
|
//- /main.rs crate:main deps:std
|
||||||
|
use std::*;
|
||||||
|
fn main() {
|
||||||
|
$0
|
||||||
|
}
|
||||||
|
//- /std.rs crate:std
|
||||||
|
#[unstable]
|
||||||
|
pub struct UnstableThisShouldNotBeListed;
|
||||||
|
"#,
|
||||||
|
expect![[r#"
|
||||||
|
fn main() fn()
|
||||||
|
md std
|
||||||
|
bt u32
|
||||||
|
kw const
|
||||||
|
kw crate::
|
||||||
|
kw enum
|
||||||
|
kw extern
|
||||||
|
kw false
|
||||||
|
kw fn
|
||||||
|
kw for
|
||||||
|
kw if
|
||||||
|
kw if let
|
||||||
|
kw impl
|
||||||
|
kw let
|
||||||
|
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 expr_unstable_item_on_nightly() {
|
||||||
|
check_empty(
|
||||||
|
r#"
|
||||||
|
//- toolchain:nightly
|
||||||
|
//- /main.rs crate:main deps:std
|
||||||
|
use std::*;
|
||||||
|
fn main() {
|
||||||
|
$0
|
||||||
|
}
|
||||||
|
//- /std.rs crate:std
|
||||||
|
#[unstable]
|
||||||
|
pub struct UnstableButWeAreOnNightlyAnyway;
|
||||||
|
"#,
|
||||||
|
expect![[r#"
|
||||||
|
fn main() fn()
|
||||||
|
md std
|
||||||
|
st UnstableButWeAreOnNightlyAnyway
|
||||||
|
bt u32
|
||||||
|
kw const
|
||||||
|
kw crate::
|
||||||
|
kw enum
|
||||||
|
kw extern
|
||||||
|
kw false
|
||||||
|
kw fn
|
||||||
|
kw for
|
||||||
|
kw if
|
||||||
|
kw if let
|
||||||
|
kw impl
|
||||||
|
kw let
|
||||||
|
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
|
||||||
|
"#]],
|
||||||
|
);
|
||||||
|
}
|
||||||
|
@ -1107,6 +1107,41 @@ fn function() {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn flyimport_pattern_no_unstable_item_on_stable() {
|
||||||
|
check(
|
||||||
|
r#"
|
||||||
|
//- /main.rs crate:main deps:std
|
||||||
|
fn function() {
|
||||||
|
let foo$0
|
||||||
|
}
|
||||||
|
//- /std.rs crate:std
|
||||||
|
#[unstable]
|
||||||
|
pub struct FooStruct {}
|
||||||
|
"#,
|
||||||
|
expect![""],
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn flyimport_pattern_unstable_item_on_nightly() {
|
||||||
|
check(
|
||||||
|
r#"
|
||||||
|
//- toolchain:nightly
|
||||||
|
//- /main.rs crate:main deps:std
|
||||||
|
fn function() {
|
||||||
|
let foo$0
|
||||||
|
}
|
||||||
|
//- /std.rs crate:std
|
||||||
|
#[unstable]
|
||||||
|
pub struct FooStruct {}
|
||||||
|
"#,
|
||||||
|
expect![[r#"
|
||||||
|
st FooStruct (use std::FooStruct)
|
||||||
|
"#]],
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn flyimport_item_name() {
|
fn flyimport_item_name() {
|
||||||
check(
|
check(
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
//! Completion tests for item list position.
|
//! Completion tests for item list position.
|
||||||
use expect_test::{expect, Expect};
|
use expect_test::{expect, Expect};
|
||||||
|
|
||||||
use crate::tests::{check_edit, completion_list, BASE_ITEMS_FIXTURE};
|
use crate::tests::{check_edit, check_empty, completion_list, BASE_ITEMS_FIXTURE};
|
||||||
|
|
||||||
fn check(ra_fixture: &str, expect: Expect) {
|
fn check(ra_fixture: &str, expect: Expect) {
|
||||||
let actual = completion_list(&format!("{BASE_ITEMS_FIXTURE}{ra_fixture}"));
|
let actual = completion_list(&format!("{BASE_ITEMS_FIXTURE}{ra_fixture}"));
|
||||||
@ -297,6 +297,58 @@ fn fn function1()
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn in_trait_impl_no_unstable_item_on_stable() {
|
||||||
|
check_empty(
|
||||||
|
r#"
|
||||||
|
trait Test {
|
||||||
|
#[unstable]
|
||||||
|
type Type;
|
||||||
|
#[unstable]
|
||||||
|
const CONST: ();
|
||||||
|
#[unstable]
|
||||||
|
fn function();
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Test for () {
|
||||||
|
$0
|
||||||
|
}
|
||||||
|
"#,
|
||||||
|
expect![[r#"
|
||||||
|
kw crate::
|
||||||
|
kw self::
|
||||||
|
"#]],
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn in_trait_impl_unstable_item_on_nightly() {
|
||||||
|
check_empty(
|
||||||
|
r#"
|
||||||
|
//- toolchain:nightly
|
||||||
|
trait Test {
|
||||||
|
#[unstable]
|
||||||
|
type Type;
|
||||||
|
#[unstable]
|
||||||
|
const CONST: ();
|
||||||
|
#[unstable]
|
||||||
|
fn function();
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Test for () {
|
||||||
|
$0
|
||||||
|
}
|
||||||
|
"#,
|
||||||
|
expect![[r#"
|
||||||
|
ct const CONST: () =
|
||||||
|
fn fn function()
|
||||||
|
ta type Type =
|
||||||
|
kw crate::
|
||||||
|
kw self::
|
||||||
|
"#]],
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn after_unit_struct() {
|
fn after_unit_struct() {
|
||||||
check(
|
check(
|
||||||
|
@ -1,12 +1,7 @@
|
|||||||
//! Completion tests for pattern position.
|
//! Completion tests for pattern position.
|
||||||
use expect_test::{expect, Expect};
|
use expect_test::{expect, Expect};
|
||||||
|
|
||||||
use crate::tests::{check_edit, completion_list, BASE_ITEMS_FIXTURE};
|
use crate::tests::{check_edit, check_empty, completion_list, BASE_ITEMS_FIXTURE};
|
||||||
|
|
||||||
fn check_empty(ra_fixture: &str, expect: Expect) {
|
|
||||||
let actual = completion_list(ra_fixture);
|
|
||||||
expect.assert_eq(&actual)
|
|
||||||
}
|
|
||||||
|
|
||||||
fn check(ra_fixture: &str, expect: Expect) {
|
fn check(ra_fixture: &str, expect: Expect) {
|
||||||
let actual = completion_list(&format!("{BASE_ITEMS_FIXTURE}\n{ra_fixture}"));
|
let actual = completion_list(&format!("{BASE_ITEMS_FIXTURE}\n{ra_fixture}"));
|
||||||
@ -742,3 +737,56 @@ fn f(x: EnumAlias<u8>) {
|
|||||||
"#]],
|
"#]],
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn pat_no_unstable_item_on_stable() {
|
||||||
|
check_empty(
|
||||||
|
r#"
|
||||||
|
//- /main.rs crate:main deps:std
|
||||||
|
use std::*;
|
||||||
|
fn foo() {
|
||||||
|
let a$0
|
||||||
|
}
|
||||||
|
//- /std.rs crate:std
|
||||||
|
#[unstable]
|
||||||
|
pub struct S;
|
||||||
|
#[unstable]
|
||||||
|
pub enum Enum {
|
||||||
|
Variant
|
||||||
|
}
|
||||||
|
"#,
|
||||||
|
expect![[r#"
|
||||||
|
md std
|
||||||
|
kw mut
|
||||||
|
kw ref
|
||||||
|
"#]],
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn pat_unstable_item_on_nightly() {
|
||||||
|
check_empty(
|
||||||
|
r#"
|
||||||
|
//- toolchain:nightly
|
||||||
|
//- /main.rs crate:main deps:std
|
||||||
|
use std::*;
|
||||||
|
fn foo() {
|
||||||
|
let a$0
|
||||||
|
}
|
||||||
|
//- /std.rs crate:std
|
||||||
|
#[unstable]
|
||||||
|
pub struct S;
|
||||||
|
#[unstable]
|
||||||
|
pub enum Enum {
|
||||||
|
Variant
|
||||||
|
}
|
||||||
|
"#,
|
||||||
|
expect![[r#"
|
||||||
|
en Enum
|
||||||
|
md std
|
||||||
|
st S
|
||||||
|
kw mut
|
||||||
|
kw ref
|
||||||
|
"#]],
|
||||||
|
);
|
||||||
|
}
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
//! Completion tests for predicates and bounds.
|
//! Completion tests for predicates and bounds.
|
||||||
use expect_test::{expect, Expect};
|
use expect_test::{expect, Expect};
|
||||||
|
|
||||||
use crate::tests::{completion_list, BASE_ITEMS_FIXTURE};
|
use crate::tests::{check_empty, completion_list, BASE_ITEMS_FIXTURE};
|
||||||
|
|
||||||
fn check(ra_fixture: &str, expect: Expect) {
|
fn check(ra_fixture: &str, expect: Expect) {
|
||||||
let actual = completion_list(&format!("{BASE_ITEMS_FIXTURE}\n{ra_fixture}"));
|
let actual = completion_list(&format!("{BASE_ITEMS_FIXTURE}\n{ra_fixture}"));
|
||||||
@ -129,3 +129,43 @@ fn method(self) where $0 {}
|
|||||||
"#]],
|
"#]],
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn pred_no_unstable_item_on_stable() {
|
||||||
|
check_empty(
|
||||||
|
r#"
|
||||||
|
//- /main.rs crate:main deps:std
|
||||||
|
use std::*;
|
||||||
|
struct Foo<T> where T: $0 {}
|
||||||
|
//- /std.rs crate:std
|
||||||
|
#[unstable]
|
||||||
|
pub trait Trait {}
|
||||||
|
"#,
|
||||||
|
expect![[r#"
|
||||||
|
md std
|
||||||
|
kw crate::
|
||||||
|
kw self::
|
||||||
|
"#]],
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn pred_unstable_item_on_nightly() {
|
||||||
|
check_empty(
|
||||||
|
r#"
|
||||||
|
//- toolchain:nightly
|
||||||
|
//- /main.rs crate:main deps:std
|
||||||
|
use std::*;
|
||||||
|
struct Foo<T> where T: $0 {}
|
||||||
|
//- /std.rs crate:std
|
||||||
|
#[unstable]
|
||||||
|
pub trait Trait {}
|
||||||
|
"#,
|
||||||
|
expect![[r#"
|
||||||
|
md std
|
||||||
|
tt Trait
|
||||||
|
kw crate::
|
||||||
|
kw self::
|
||||||
|
"#]],
|
||||||
|
);
|
||||||
|
}
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
//! Completion tests for type position.
|
//! Completion tests for type position.
|
||||||
use expect_test::{expect, Expect};
|
use expect_test::{expect, Expect};
|
||||||
|
|
||||||
use crate::tests::{completion_list, BASE_ITEMS_FIXTURE};
|
use crate::tests::{check_empty, completion_list, BASE_ITEMS_FIXTURE};
|
||||||
|
|
||||||
fn check(ra_fixture: &str, expect: Expect) {
|
fn check(ra_fixture: &str, expect: Expect) {
|
||||||
let actual = completion_list(&format!("{BASE_ITEMS_FIXTURE}\n{ra_fixture}"));
|
let actual = completion_list(&format!("{BASE_ITEMS_FIXTURE}\n{ra_fixture}"));
|
||||||
@ -669,3 +669,53 @@ fn f(t: impl MyTrait<Item1 = u8, Item2 = $0
|
|||||||
"#]],
|
"#]],
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn type_pos_no_unstable_type_on_stable() {
|
||||||
|
check_empty(
|
||||||
|
r#"
|
||||||
|
//- /main.rs crate:main deps:std
|
||||||
|
use std::*;
|
||||||
|
struct Foo {
|
||||||
|
f: $0
|
||||||
|
}
|
||||||
|
//- /std.rs crate:std
|
||||||
|
#[unstable]
|
||||||
|
pub struct S;
|
||||||
|
"#,
|
||||||
|
expect![[r#"
|
||||||
|
md std
|
||||||
|
sp Self
|
||||||
|
st Foo
|
||||||
|
bt u32
|
||||||
|
kw crate::
|
||||||
|
kw self::
|
||||||
|
"#]],
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn type_pos_unstable_type_on_nightly() {
|
||||||
|
check_empty(
|
||||||
|
r#"
|
||||||
|
//- toolchain:nightly
|
||||||
|
//- /main.rs crate:main deps:std
|
||||||
|
use std::*;
|
||||||
|
struct Foo {
|
||||||
|
f: $0
|
||||||
|
}
|
||||||
|
//- /std.rs crate:std
|
||||||
|
#[unstable]
|
||||||
|
pub struct S;
|
||||||
|
"#,
|
||||||
|
expect![[r#"
|
||||||
|
md std
|
||||||
|
sp Self
|
||||||
|
st Foo
|
||||||
|
st S
|
||||||
|
bt u32
|
||||||
|
kw crate::
|
||||||
|
kw self::
|
||||||
|
"#]],
|
||||||
|
)
|
||||||
|
}
|
||||||
|
@ -382,3 +382,51 @@ fn bar fn(u32)
|
|||||||
"#]],
|
"#]],
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn use_tree_no_unstable_items_on_stable() {
|
||||||
|
check(
|
||||||
|
r#"
|
||||||
|
//- toolchain:stable
|
||||||
|
//- /lib.rs crate:main deps:std
|
||||||
|
use std::$0
|
||||||
|
//- /std.rs crate:std
|
||||||
|
#[unstable]
|
||||||
|
pub mod simd {}
|
||||||
|
#[unstable]
|
||||||
|
pub struct S;
|
||||||
|
#[unstable]
|
||||||
|
pub fn foo() {}
|
||||||
|
#[unstable]
|
||||||
|
#[macro_export]
|
||||||
|
marco_rules! m { () => {} }
|
||||||
|
"#,
|
||||||
|
expect![""],
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn use_tree_unstable_items_on_nightly() {
|
||||||
|
check(
|
||||||
|
r#"
|
||||||
|
//- toolchain:nightly
|
||||||
|
//- /lib.rs crate:main deps:std
|
||||||
|
use std::$0
|
||||||
|
//- /std.rs crate:std
|
||||||
|
#[unstable]
|
||||||
|
pub mod simd {}
|
||||||
|
#[unstable]
|
||||||
|
pub struct S;
|
||||||
|
#[unstable]
|
||||||
|
pub fn foo() {}
|
||||||
|
#[unstable]
|
||||||
|
#[macro_export]
|
||||||
|
marco_rules! m { () => {} }
|
||||||
|
"#,
|
||||||
|
expect![[r#"
|
||||||
|
fn foo fn()
|
||||||
|
md simd
|
||||||
|
st S
|
||||||
|
"#]],
|
||||||
|
);
|
||||||
|
}
|
||||||
|
@ -116,7 +116,7 @@ fn external_docs_doc_url_std_crate() {
|
|||||||
//- /main.rs crate:std
|
//- /main.rs crate:std
|
||||||
use self$0;
|
use self$0;
|
||||||
"#,
|
"#,
|
||||||
expect![[r#"https://doc.rust-lang.org/nightly/std/index.html"#]],
|
expect!["https://doc.rust-lang.org/stable/std/index.html"],
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -4242,7 +4242,7 @@ fn foo() {
|
|||||||
/// [threads]: ../book/ch16-01-threads.html#using-move-closures-with-threads
|
/// [threads]: ../book/ch16-01-threads.html#using-move-closures-with-threads
|
||||||
mod move_keyword {}
|
mod move_keyword {}
|
||||||
"#,
|
"#,
|
||||||
expect![[r##"
|
expect![[r#"
|
||||||
*move*
|
*move*
|
||||||
|
|
||||||
```rust
|
```rust
|
||||||
@ -4251,11 +4251,11 @@ mod move_keyword {}
|
|||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
[closure](https://doc.rust-lang.org/nightly/book/ch13-01-closures.html)
|
[closure](https://doc.rust-lang.org/stable/book/ch13-01-closures.html)
|
||||||
[closures](https://doc.rust-lang.org/nightly/book/ch13-01-closures.html)
|
[closures](https://doc.rust-lang.org/stable/book/ch13-01-closures.html)
|
||||||
[threads](https://doc.rust-lang.org/nightly/book/ch16-01-threads.html#using-move-closures-with-threads)
|
[threads](https://doc.rust-lang.org/stable/book/ch16-01-threads.html#using-move-closures-with-threads)
|
||||||
<https://doc.rust-lang.org/nightly/book/ch13-01-closures.html>
|
<https://doc.rust-lang.org/nightly/book/ch13-01-closures.html>
|
||||||
"##]],
|
"#]],
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -13,7 +13,7 @@
|
|||||||
use rust_analyzer::{config::Config, lsp_ext, main_loop};
|
use rust_analyzer::{config::Config, lsp_ext, main_loop};
|
||||||
use serde::Serialize;
|
use serde::Serialize;
|
||||||
use serde_json::{json, to_string_pretty, Value};
|
use serde_json::{json, to_string_pretty, Value};
|
||||||
use test_utils::Fixture;
|
use test_utils::FixtureWithProjectMeta;
|
||||||
use vfs::AbsPathBuf;
|
use vfs::AbsPathBuf;
|
||||||
|
|
||||||
use crate::testdir::TestDir;
|
use crate::testdir::TestDir;
|
||||||
@ -84,10 +84,12 @@ pub(crate) fn server(self) -> Server {
|
|||||||
profile::init_from(crate::PROFILE);
|
profile::init_from(crate::PROFILE);
|
||||||
});
|
});
|
||||||
|
|
||||||
let (mini_core, proc_macros, fixtures) = Fixture::parse(self.fixture);
|
let FixtureWithProjectMeta { fixture, mini_core, proc_macro_names, toolchain } =
|
||||||
assert!(proc_macros.is_empty());
|
FixtureWithProjectMeta::parse(self.fixture);
|
||||||
|
assert!(proc_macro_names.is_empty());
|
||||||
assert!(mini_core.is_none());
|
assert!(mini_core.is_none());
|
||||||
for entry in fixtures {
|
assert!(toolchain.is_none());
|
||||||
|
for entry in fixture {
|
||||||
let path = tmp_dir.path().join(&entry.path['/'.len_utf8()..]);
|
let path = tmp_dir.path().join(&entry.path['/'.len_utf8()..]);
|
||||||
fs::create_dir_all(path.parent().unwrap()).unwrap();
|
fs::create_dir_all(path.parent().unwrap()).unwrap();
|
||||||
fs::write(path.as_path(), entry.text.as_bytes()).unwrap();
|
fs::write(path.as_path(), entry.text.as_bytes()).unwrap();
|
||||||
|
@ -86,7 +86,14 @@ pub struct MiniCore {
|
|||||||
valid_flags: Vec<String>,
|
valid_flags: Vec<String>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Fixture {
|
pub struct FixtureWithProjectMeta {
|
||||||
|
pub fixture: Vec<Fixture>,
|
||||||
|
pub mini_core: Option<MiniCore>,
|
||||||
|
pub proc_macro_names: Vec<String>,
|
||||||
|
pub toolchain: Option<String>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl FixtureWithProjectMeta {
|
||||||
/// Parses text which looks like this:
|
/// Parses text which looks like this:
|
||||||
///
|
///
|
||||||
/// ```not_rust
|
/// ```not_rust
|
||||||
@ -96,37 +103,40 @@ impl Fixture {
|
|||||||
/// //- other meta
|
/// //- other meta
|
||||||
/// ```
|
/// ```
|
||||||
///
|
///
|
||||||
/// Fixture can also start with a proc_macros and minicore declaration(in that order):
|
/// Fixture can also start with a proc_macros and minicore declaration (in that order):
|
||||||
///
|
///
|
||||||
/// ```
|
/// ```
|
||||||
|
/// //- toolchain: nightly
|
||||||
/// //- proc_macros: identity
|
/// //- proc_macros: identity
|
||||||
/// //- minicore: sized
|
/// //- minicore: sized
|
||||||
/// ```
|
/// ```
|
||||||
///
|
///
|
||||||
/// That will include predefined proc macros and a subset of `libcore` into the fixture, see
|
/// That will include predefined proc macros and a subset of `libcore` into the fixture, see
|
||||||
/// `minicore.rs` for what's available.
|
/// `minicore.rs` for what's available.
|
||||||
pub fn parse(ra_fixture: &str) -> (Option<MiniCore>, Vec<String>, Vec<Fixture>) {
|
pub fn parse(ra_fixture: &str) -> Self {
|
||||||
let fixture = trim_indent(ra_fixture);
|
let fixture = trim_indent(ra_fixture);
|
||||||
let mut fixture = fixture.as_str();
|
let mut fixture = fixture.as_str();
|
||||||
|
let mut toolchain = None;
|
||||||
let mut mini_core = None;
|
let mut mini_core = None;
|
||||||
let mut res: Vec<Fixture> = Vec::new();
|
let mut res: Vec<Fixture> = Vec::new();
|
||||||
let mut test_proc_macros = vec![];
|
let mut proc_macro_names = vec![];
|
||||||
|
|
||||||
if fixture.starts_with("//- proc_macros:") {
|
if let Some(meta) = fixture.strip_prefix("//- toolchain:") {
|
||||||
let first_line = fixture.split_inclusive('\n').next().unwrap();
|
let (meta, remain) = meta.split_once('\n').unwrap();
|
||||||
test_proc_macros = first_line
|
toolchain = Some(meta.trim().to_string());
|
||||||
.strip_prefix("//- proc_macros:")
|
fixture = remain;
|
||||||
.unwrap()
|
|
||||||
.split(',')
|
|
||||||
.map(|it| it.trim().to_string())
|
|
||||||
.collect();
|
|
||||||
fixture = &fixture[first_line.len()..];
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if fixture.starts_with("//- minicore:") {
|
if let Some(meta) = fixture.strip_prefix("//- proc_macros:") {
|
||||||
let first_line = fixture.split_inclusive('\n').next().unwrap();
|
let (meta, remain) = meta.split_once('\n').unwrap();
|
||||||
mini_core = Some(MiniCore::parse(first_line));
|
proc_macro_names = meta.split(',').map(|it| it.trim().to_string()).collect();
|
||||||
fixture = &fixture[first_line.len()..];
|
fixture = remain;
|
||||||
|
}
|
||||||
|
|
||||||
|
if let Some(meta) = fixture.strip_prefix("//- minicore:") {
|
||||||
|
let (meta, remain) = meta.split_once('\n').unwrap();
|
||||||
|
mini_core = Some(MiniCore::parse(meta));
|
||||||
|
fixture = remain;
|
||||||
}
|
}
|
||||||
|
|
||||||
let default = if fixture.contains("//-") { None } else { Some("//- /main.rs") };
|
let default = if fixture.contains("//-") { None } else { Some("//- /main.rs") };
|
||||||
@ -142,7 +152,7 @@ pub fn parse(ra_fixture: &str) -> (Option<MiniCore>, Vec<String>, Vec<Fixture>)
|
|||||||
}
|
}
|
||||||
|
|
||||||
if line.starts_with("//-") {
|
if line.starts_with("//-") {
|
||||||
let meta = Fixture::parse_meta_line(line);
|
let meta = Self::parse_meta_line(line);
|
||||||
res.push(meta);
|
res.push(meta);
|
||||||
} else {
|
} else {
|
||||||
if line.starts_with("// ")
|
if line.starts_with("// ")
|
||||||
@ -160,7 +170,7 @@ pub fn parse(ra_fixture: &str) -> (Option<MiniCore>, Vec<String>, Vec<Fixture>)
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
(mini_core, test_proc_macros, res)
|
Self { fixture: res, mini_core, proc_macro_names, toolchain }
|
||||||
}
|
}
|
||||||
|
|
||||||
//- /lib.rs crate:foo deps:bar,baz cfg:foo=a,bar=b env:OUTDIR=path/to,OTHER=foo
|
//- /lib.rs crate:foo deps:bar,baz cfg:foo=a,bar=b env:OUTDIR=path/to,OTHER=foo
|
||||||
@ -257,8 +267,7 @@ fn assert_valid_flag(&self, flag: &str) {
|
|||||||
fn parse(line: &str) -> MiniCore {
|
fn parse(line: &str) -> MiniCore {
|
||||||
let mut res = MiniCore { activated_flags: Vec::new(), valid_flags: Vec::new() };
|
let mut res = MiniCore { activated_flags: Vec::new(), valid_flags: Vec::new() };
|
||||||
|
|
||||||
let line = line.strip_prefix("//- minicore:").unwrap().trim();
|
for entry in line.trim().split(", ") {
|
||||||
for entry in line.split(", ") {
|
|
||||||
if res.has_flag(entry) {
|
if res.has_flag(entry) {
|
||||||
panic!("duplicate minicore flag: {entry:?}");
|
panic!("duplicate minicore flag: {entry:?}");
|
||||||
}
|
}
|
||||||
@ -372,7 +381,7 @@ pub fn source_code(mut self) -> String {
|
|||||||
#[test]
|
#[test]
|
||||||
#[should_panic]
|
#[should_panic]
|
||||||
fn parse_fixture_checks_further_indented_metadata() {
|
fn parse_fixture_checks_further_indented_metadata() {
|
||||||
Fixture::parse(
|
FixtureWithProjectMeta::parse(
|
||||||
r"
|
r"
|
||||||
//- /lib.rs
|
//- /lib.rs
|
||||||
mod bar;
|
mod bar;
|
||||||
@ -386,15 +395,18 @@ pub fn baz() {}
|
|||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn parse_fixture_gets_full_meta() {
|
fn parse_fixture_gets_full_meta() {
|
||||||
let (mini_core, proc_macros, parsed) = Fixture::parse(
|
let FixtureWithProjectMeta { fixture: parsed, mini_core, proc_macro_names, toolchain } =
|
||||||
r#"
|
FixtureWithProjectMeta::parse(
|
||||||
|
r#"
|
||||||
|
//- toolchain: nightly
|
||||||
//- proc_macros: identity
|
//- proc_macros: identity
|
||||||
//- minicore: coerce_unsized
|
//- minicore: coerce_unsized
|
||||||
//- /lib.rs crate:foo deps:bar,baz cfg:foo=a,bar=b,atom env:OUTDIR=path/to,OTHER=foo
|
//- /lib.rs crate:foo deps:bar,baz cfg:foo=a,bar=b,atom env:OUTDIR=path/to,OTHER=foo
|
||||||
mod m;
|
mod m;
|
||||||
"#,
|
"#,
|
||||||
);
|
);
|
||||||
assert_eq!(proc_macros, vec!["identity".to_string()]);
|
assert_eq!(toolchain, Some("nightly".to_string()));
|
||||||
|
assert_eq!(proc_macro_names, vec!["identity".to_string()]);
|
||||||
assert_eq!(mini_core.unwrap().activated_flags, vec!["coerce_unsized".to_string()]);
|
assert_eq!(mini_core.unwrap().activated_flags, vec!["coerce_unsized".to_string()]);
|
||||||
assert_eq!(1, parsed.len());
|
assert_eq!(1, parsed.len());
|
||||||
|
|
||||||
|
@ -27,7 +27,7 @@
|
|||||||
|
|
||||||
pub use crate::{
|
pub use crate::{
|
||||||
assert_linear::AssertLinear,
|
assert_linear::AssertLinear,
|
||||||
fixture::{Fixture, MiniCore},
|
fixture::{Fixture, FixtureWithProjectMeta, MiniCore},
|
||||||
};
|
};
|
||||||
|
|
||||||
pub const CURSOR_MARKER: &str = "$0";
|
pub const CURSOR_MARKER: &str = "$0";
|
||||||
|
Loading…
Reference in New Issue
Block a user