diff options
| author | bors <bors@rust-lang.org> | 2023-04-11 14:34:47 +0000 |
|---|---|---|
| committer | bors <bors@rust-lang.org> | 2023-04-11 14:34:47 +0000 |
| commit | fd276218ecdfd64f915460be3b68aaa5dc58a18e (patch) | |
| tree | 26af886bc27885387c44bfe9f2f8c60d3eb5dd05 | |
| parent | 600283f2de12b40fbe60a6cade3650785ef0bbbc (diff) | |
| parent | e6e48728da1c4500304e022d6dfe29bb5afb0699 (diff) | |
| download | rust-fd276218ecdfd64f915460be3b68aaa5dc58a18e.tar.gz rust-fd276218ecdfd64f915460be3b68aaa5dc58a18e.zip | |
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.
21 files changed, 602 insertions, 80 deletions
diff --git a/crates/base-db/src/fixture.rs b/crates/base-db/src/fixture.rs index 3339580bfe2..f01f75b1387 100644 --- a/crates/base-db/src/fixture.rs +++ b/crates/base-db/src/fixture.rs @@ -4,7 +4,8 @@ use std::{mem, str::FromStr, sync::Arc}; use cfg::CfgOptions; use rustc_hash::FxHashMap; 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 vfs::{file_set::FileSet, VfsPath}; @@ -12,7 +13,7 @@ use vfs::{file_set::FileSet, VfsPath}; use crate::{ input::{CrateName, CrateOrigin, LangCrateOrigin}, Change, CrateDisplayName, CrateGraph, CrateId, Dependency, Edition, Env, FileId, FilePosition, - FileRange, ProcMacro, ProcMacroExpander, ProcMacroExpansionError, ProcMacros, + FileRange, ProcMacro, ProcMacroExpander, ProcMacroExpansionError, ProcMacros, ReleaseChannel, SourceDatabaseExt, SourceRoot, SourceRootId, }; @@ -102,7 +103,14 @@ impl ChangeFixture { ra_fixture: &str, mut proc_macro_defs: Vec<(String, ProcMacro)>, ) -> 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 files = Vec::new(); @@ -166,7 +174,7 @@ impl ChangeFixture { .as_deref() .map(Arc::from) .ok_or_else(|| "target_data_layout unset".into()), - None, + Some(toolchain), ); let prev = crates.insert(crate_name.clone(), crate_id); assert!(prev.is_none()); @@ -205,7 +213,7 @@ impl ChangeFixture { default_target_data_layout .map(|x| x.into()) .ok_or_else(|| "target_data_layout unset".into()), - None, + Some(toolchain), ); } else { for (from, to, prelude) in crate_deps { @@ -247,7 +255,7 @@ impl ChangeFixture { false, CrateOrigin::Lang(LangCrateOrigin::Core), target_layout.clone(), - None, + Some(toolchain), ); for krate in all_crates { @@ -286,7 +294,7 @@ impl ChangeFixture { true, CrateOrigin::Local { repo: None, name: None }, target_layout, - None, + Some(toolchain), ); proc_macros.insert(proc_macros_crate, Ok(proc_macro)); diff --git a/crates/hir-def/src/attr.rs b/crates/hir-def/src/attr.rs index 9224773aa80..cc59e780938 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 b6a066f4f51..6f23bc5c747 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<syntax::SmolStr>, ) { + 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<syntax::SmolStr>, ) { + 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<hir::Name>, ) { + 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<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) { 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<hir::Name>, ) { + 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<hir::ModPath>, local_name: Option<hir::Name>, ) { + 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<hir::ModPath>, 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); 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<hir::Name>, ) { + 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<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)); } } diff --git a/crates/ide-completion/src/completions/dot.rs b/crates/ide-completion/src/completions/dot.rs index 77246379e7b..ba766340161 100644 --- a/crates/ide-completion/src/completions/dot.rs +++ b/crates/ide-completion/src/completions/dot.rs @@ -173,6 +173,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] fn test_struct_field_completion_self() { check( r#" diff --git a/crates/ide-completion/src/completions/flyimport.rs b/crates/ide-completion/src/completions/flyimport.rs index 0979f6a6dfc..32d3fb8c624 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 889d90095fa..e82908a361e 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 546a1f4c499..45be4fb2054 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 f6478d2ceb2..d99414fe358 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, diff --git a/crates/ide-completion/src/tests.rs b/crates/ide-completion/src/tests.rs index 05e6aaf09d9..79c1f98f3bd 100644 --- a/crates/ide-completion/src/tests.rs +++ b/crates/ide-completion/src/tests.rs @@ -23,6 +23,7 @@ mod type_pos; mod use_tree; mod visibility; +use expect_test::Expect; use hir::PrefixKind; use ide_db::{ base_db::{fixture::ChangeFixture, FileLoader, FilePosition}, @@ -215,6 +216,11 @@ pub(crate) fn check_edit_with_config( 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( config: CompletionConfig, code: &str, diff --git a/crates/ide-completion/src/tests/expression.rs b/crates/ide-completion/src/tests/expression.rs index c1c6a689eb1..36465be23d8 100644 --- a/crates/ide-completion/src/tests/expression.rs +++ b/crates/ide-completion/src/tests/expression.rs @@ -1,18 +1,13 @@ //! Completion tests for expressions. 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) { let actual = completion_list(&format!("{BASE_ITEMS_FIXTURE}{ra_fixture}")); expect.assert_eq(&actual) } -fn check_empty(ra_fixture: &str, expect: Expect) { - let actual = completion_list(ra_fixture); - expect.assert_eq(&actual); -} - #[test] fn complete_literal_struct_with_a_private_field() { // `FooDesc.bar` is private, the completion should not be triggered. @@ -997,3 +992,105 @@ fn foo() { if foo {} el$0 { let x = 92; } } "#]], ); } + +#[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 + "#]], + ); +} diff --git a/crates/ide-completion/src/tests/flyimport.rs b/crates/ide-completion/src/tests/flyimport.rs index 0b485eb776d..d727320b514 100644 --- a/crates/ide-completion/src/tests/flyimport.rs +++ b/crates/ide-completion/src/tests/flyimport.rs @@ -1108,6 +1108,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] fn flyimport_item_name() { check( r#" diff --git a/crates/ide-completion/src/tests/item_list.rs b/crates/ide-completion/src/tests/item_list.rs index 9fc731bb11d..2b5b4dd773c 100644 --- a/crates/ide-completion/src/tests/item_list.rs +++ b/crates/ide-completion/src/tests/item_list.rs @@ -1,7 +1,7 @@ //! Completion tests for item list position. 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) { let actual = completion_list(&format!("{BASE_ITEMS_FIXTURE}{ra_fixture}")); @@ -298,6 +298,58 @@ impl Test for () { } #[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] fn after_unit_struct() { check( r#"struct S; f$0"#, diff --git a/crates/ide-completion/src/tests/pattern.rs b/crates/ide-completion/src/tests/pattern.rs index c0e485c36fd..8af6cce98f6 100644 --- a/crates/ide-completion/src/tests/pattern.rs +++ b/crates/ide-completion/src/tests/pattern.rs @@ -1,12 +1,7 @@ //! Completion tests for pattern position. use expect_test::{expect, Expect}; -use crate::tests::{check_edit, completion_list, BASE_ITEMS_FIXTURE}; - -fn check_empty(ra_fixture: &str, expect: Expect) { - let actual = completion_list(ra_fixture); - expect.assert_eq(&actual) -} +use crate::tests::{check_edit, check_empty, completion_list, BASE_ITEMS_FIXTURE}; fn check(ra_fixture: &str, expect: Expect) { 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 + "#]], + ); +} diff --git a/crates/ide-completion/src/tests/predicate.rs b/crates/ide-completion/src/tests/predicate.rs index 2656a4d545e..789ad66345b 100644 --- a/crates/ide-completion/src/tests/predicate.rs +++ b/crates/ide-completion/src/tests/predicate.rs @@ -1,7 +1,7 @@ //! Completion tests for predicates and bounds. 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) { let actual = completion_list(&format!("{BASE_ITEMS_FIXTURE}\n{ra_fixture}")); @@ -129,3 +129,43 @@ impl Record { "#]], ); } + +#[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:: + "#]], + ); +} diff --git a/crates/ide-completion/src/tests/type_pos.rs b/crates/ide-completion/src/tests/type_pos.rs index c3f4fb4d181..8cb1ff4a125 100644 --- a/crates/ide-completion/src/tests/type_pos.rs +++ b/crates/ide-completion/src/tests/type_pos.rs @@ -1,7 +1,7 @@ //! Completion tests for type position. 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) { 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:: + "#]], + ) +} diff --git a/crates/ide-completion/src/tests/use_tree.rs b/crates/ide-completion/src/tests/use_tree.rs index 037d7dce52e..ba2e047999e 100644 --- a/crates/ide-completion/src/tests/use_tree.rs +++ b/crates/ide-completion/src/tests/use_tree.rs @@ -382,3 +382,51 @@ use self::foo::impl$0 "#]], ); } + +#[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 + "#]], + ); +} diff --git a/crates/ide/src/doc_links/tests.rs b/crates/ide/src/doc_links/tests.rs index 104181a33e6..b6b46c45088 100644 --- a/crates/ide/src/doc_links/tests.rs +++ b/crates/ide/src/doc_links/tests.rs @@ -116,7 +116,7 @@ fn external_docs_doc_url_std_crate() { //- /main.rs crate:std use self$0; "#, - expect![[r#"https://doc.rust-lang.org/nightly/std/index.html"#]], + expect!["https://doc.rust-lang.org/stable/std/index.html"], ); } diff --git a/crates/ide/src/hover/tests.rs b/crates/ide/src/hover/tests.rs index 8a58fbeb860..73ab3d2e16f 100644 --- a/crates/ide/src/hover/tests.rs +++ b/crates/ide/src/hover/tests.rs @@ -4242,7 +4242,7 @@ fn foo() { /// [threads]: ../book/ch16-01-threads.html#using-move-closures-with-threads mod move_keyword {} "#, - expect - [closures](https://doc.rust-lang.org/nightly/book/ch13-01-closures.html) - [threads](https://doc.rust-lang.org/nightly/book/ch16-01-threads.html#using-move-closures-with-threads) + [closure](https://doc.rust-lang.org/stable/book/ch13-01-closures.html) + [closures](https://doc.rust-lang.org/stable/book/ch13-01-closures.html) + [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> - "##]], + "#]], ); } diff --git a/crates/rust-analyzer/tests/slow-tests/support.rs b/crates/rust-analyzer/tests/slow-tests/support.rs index e5f0e57d2a0..df62dcd7ddc 100644 --- a/crates/rust-analyzer/tests/slow-tests/support.rs +++ b/crates/rust-analyzer/tests/slow-tests/support.rs @@ -13,7 +13,7 @@ use project_model::ProjectManifest; use rust_analyzer::{config::Config, lsp_ext, main_loop}; use serde::Serialize; use serde_json::{json, to_string_pretty, Value}; -use test_utils::Fixture; +use test_utils::FixtureWithProjectMeta; use vfs::AbsPathBuf; use crate::testdir::TestDir; @@ -84,10 +84,12 @@ impl<'a> Project<'a> { profile::init_from(crate::PROFILE); }); - let (mini_core, proc_macros, fixtures) = Fixture::parse(self.fixture); - assert!(proc_macros.is_empty()); + let FixtureWithProjectMeta { fixture, mini_core, proc_macro_names, toolchain } = + FixtureWithProjectMeta::parse(self.fixture); + assert!(proc_macro_names.is_empty()); 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()..]); fs::create_dir_all(path.parent().unwrap()).unwrap(); fs::write(path.as_path(), entry.text.as_bytes()).unwrap(); diff --git a/crates/test-utils/src/fixture.rs b/crates/test-utils/src/fixture.rs index cd1235fa6dc..dffc7fccdab 100644 --- a/crates/test-utils/src/fixture.rs +++ b/crates/test-utils/src/fixture.rs @@ -86,7 +86,14 @@ pub struct MiniCore { 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: /// /// ```not_rust @@ -96,37 +103,40 @@ impl Fixture { /// //- 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 /// //- minicore: sized /// ``` /// /// That will include predefined proc macros and a subset of `libcore` into the fixture, see /// `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 mut fixture = fixture.as_str(); + let mut toolchain = None; let mut mini_core = None; let mut res: Vec<Fixture> = Vec::new(); - let mut test_proc_macros = vec![]; - - if fixture.starts_with("//- proc_macros:") { - let first_line = fixture.split_inclusive('\n').next().unwrap(); - test_proc_macros = first_line - .strip_prefix("//- proc_macros:") - .unwrap() - .split(',') - .map(|it| it.trim().to_string()) - .collect(); - fixture = &fixture[first_line.len()..]; + let mut proc_macro_names = vec![]; + + if let Some(meta) = fixture.strip_prefix("//- toolchain:") { + let (meta, remain) = meta.split_once('\n').unwrap(); + toolchain = Some(meta.trim().to_string()); + fixture = remain; } - if fixture.starts_with("//- minicore:") { - let first_line = fixture.split_inclusive('\n').next().unwrap(); - mini_core = Some(MiniCore::parse(first_line)); - fixture = &fixture[first_line.len()..]; + if let Some(meta) = fixture.strip_prefix("//- proc_macros:") { + let (meta, remain) = meta.split_once('\n').unwrap(); + proc_macro_names = meta.split(',').map(|it| it.trim().to_string()).collect(); + 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") }; @@ -142,7 +152,7 @@ impl Fixture { } if line.starts_with("//-") { - let meta = Fixture::parse_meta_line(line); + let meta = Self::parse_meta_line(line); res.push(meta); } else { if line.starts_with("// ") @@ -160,7 +170,7 @@ impl 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 @@ -257,8 +267,7 @@ impl MiniCore { fn parse(line: &str) -> MiniCore { let mut res = MiniCore { activated_flags: Vec::new(), valid_flags: Vec::new() }; - let line = line.strip_prefix("//- minicore:").unwrap().trim(); - for entry in line.split(", ") { + for entry in line.trim().split(", ") { if res.has_flag(entry) { panic!("duplicate minicore flag: {entry:?}"); } @@ -372,7 +381,7 @@ impl MiniCore { #[test] #[should_panic] fn parse_fixture_checks_further_indented_metadata() { - Fixture::parse( + FixtureWithProjectMeta::parse( r" //- /lib.rs mod bar; @@ -386,15 +395,18 @@ fn parse_fixture_checks_further_indented_metadata() { #[test] fn parse_fixture_gets_full_meta() { - let (mini_core, proc_macros, parsed) = Fixture::parse( - r#" + let FixtureWithProjectMeta { fixture: parsed, mini_core, proc_macro_names, toolchain } = + FixtureWithProjectMeta::parse( + r#" +//- toolchain: nightly //- proc_macros: identity //- minicore: coerce_unsized //- /lib.rs crate:foo deps:bar,baz cfg:foo=a,bar=b,atom env:OUTDIR=path/to,OTHER=foo 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!(1, parsed.len()); diff --git a/crates/test-utils/src/lib.rs b/crates/test-utils/src/lib.rs index a7a52e08e75..5abadaad629 100644 --- a/crates/test-utils/src/lib.rs +++ b/crates/test-utils/src/lib.rs @@ -27,7 +27,7 @@ pub use rustc_hash::FxHashMap; pub use crate::{ assert_linear::AssertLinear, - fixture::{Fixture, MiniCore}, + fixture::{Fixture, FixtureWithProjectMeta, MiniCore}, }; pub const CURSOR_MARKER: &str = "$0"; |
