diff --git a/Cargo.lock b/Cargo.lock index 7a2e2d493b59..0819cb669cf7 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1043,6 +1043,7 @@ dependencies = [ "arrayvec", "cfg", "cov-mark", + "dashmap", "dot", "either", "expect-test", diff --git a/crates/ide/Cargo.toml b/crates/ide/Cargo.toml index 08ffd391c02d..6a2f5fd2c2f6 100644 --- a/crates/ide/Cargo.toml +++ b/crates/ide/Cargo.toml @@ -15,6 +15,7 @@ doctest = false [dependencies] cov-mark = "2.0.0" arrayvec.workspace = true +dashmap.workspace = true either.workspace = true itertools.workspace = true tracing.workspace = true diff --git a/crates/ide/src/lib.rs b/crates/ide/src/lib.rs index c4a3ec1e8eae..eac1d03073f5 100644 --- a/crates/ide/src/lib.rs +++ b/crates/ide/src/lib.rs @@ -240,7 +240,7 @@ impl Default for AnalysisHost { /// entry point for asking semantic information about the world. When the world /// state is advanced using `AnalysisHost::apply_change` method, all existing /// `Analysis` are canceled (most method return `Err(Canceled)`). -#[derive(Debug)] +#[derive(Clone, Debug)] pub struct Analysis { db: RootDatabase, } diff --git a/crates/ide/src/static_index.rs b/crates/ide/src/static_index.rs index 9e8d772cb3d1..fada0e18b5f3 100644 --- a/crates/ide/src/static_index.rs +++ b/crates/ide/src/static_index.rs @@ -1,11 +1,20 @@ //! This module provides `StaticIndex` which is used for powering //! read-only code browsers and emitting LSIF +use std::{ + collections::VecDeque, + sync::atomic::{AtomicUsize, Ordering}, +}; + use arrayvec::ArrayVec; +use dashmap::{ + DashMap, + mapref::one::{Ref, RefMut}, +}; use either::Either; -use hir::{Crate, Module, Semantics, db::HirDatabase}; +use hir::{Crate, InFile, Module, Semantics, db::HirDatabase}; use ide_db::{ - FileId, FileRange, FxHashMap, FxHashSet, RootDatabase, + FileId, FileRange, FxHashMap, RootDatabase, base_db::{SourceDatabase, VfsPath}, defs::{Definition, IdentClass}, documentation::Documentation, @@ -26,12 +35,9 @@ use crate::{ /// /// The intended use-case is powering read-only code browsers and emitting LSIF/SCIP. #[derive(Debug)] -pub struct StaticIndex<'a> { +pub struct StaticIndex { pub files: Vec, pub tokens: TokenStore, - analysis: &'a Analysis, - db: &'a RootDatabase, - def_map: FxHashMap, TokenId>, } #[derive(Debug)] @@ -87,25 +93,28 @@ impl TokenId { } #[derive(Default, Debug)] -pub struct TokenStore(Vec); +pub struct TokenStore { + data: DashMap, + last_index: AtomicUsize, +} impl TokenStore { - pub fn insert(&mut self, data: TokenStaticData) -> TokenId { - let id = TokenId(self.0.len()); - self.0.push(data); + pub fn insert(&self, data: TokenStaticData) -> TokenId { + let id = TokenId(self.last_index.fetch_add(1, Ordering::Relaxed)); + self.data.insert(id, data); id } - pub fn get_mut(&mut self, id: TokenId) -> Option<&mut TokenStaticData> { - self.0.get_mut(id.0) + pub fn get_mut(&self, id: TokenId) -> Option> { + self.data.get_mut(&id) } - pub fn get(&self, id: TokenId) -> Option<&TokenStaticData> { - self.0.get(id.0) + pub fn get(&self, id: TokenId) -> Option> { + self.data.get(&id) } pub fn iter(self) -> impl Iterator { - self.0.into_iter().enumerate().map(|(id, data)| (TokenId(id), data)) + self.data.into_iter() } } @@ -164,45 +173,50 @@ pub enum VendoredLibrariesConfig<'a> { Excluded, } -impl<'a> StaticIndex<'a> { - fn add_file(&mut self, file_id: FileId) { - let current_crate = crates_for(self.db, file_id).pop().map(Into::into); - let folds = self.analysis.folding_ranges(file_id, true).unwrap(); - // hovers - let sema = hir::Semantics::new(self.db); - let root = sema.parse_guess_edition(file_id).syntax().clone(); - let edition = sema.attach_first_edition(file_id).edition(sema.db); - let display_target = match sema.first_crate(file_id) { - Some(krate) => krate.to_display_target(sema.db), - None => return, - }; - let tokens = root.descendants_with_tokens().filter_map(|it| match it { - syntax::NodeOrToken::Node(_) => None, - syntax::NodeOrToken::Token(it) => Some(it), - }); - let hover_config = HoverConfig { - links_in_hover: true, - memory_layout: None, - documentation: true, - keywords: true, - format: crate::HoverDocFormat::Markdown, - max_trait_assoc_items_count: None, - max_fields_count: Some(5), - max_enum_variants_count: Some(5), - max_subst_ty_len: SubstTyLen::Unlimited, - show_drop_glue: true, - ra_fixture: RaFixtureConfig::default(), - }; - let mut result = StaticIndexedFile { file_id, folds, tokens: vec![] }; +/// Computes the index for a single file identified by file_id. +/// Note this doesn't return a single StaticIndexedFile but multiple because this also indexes include! expansions. +fn index_file<'a>( + analysis: &'a Analysis, + token_store: &TokenStore, + def_map: &DashMap, TokenId>, + file_id: FileId, + with_folds: bool, + with_hover: bool, +) -> FxHashMap { + let db = &analysis.db; + + let current_crate = crates_for(db, file_id).pop().map(Into::into); + let folds = if with_folds { analysis.folding_ranges(file_id, true).unwrap() } else { vec![] }; + // hovers + let sema = hir::Semantics::new(db); + let root = sema.parse_guess_edition(file_id).syntax().clone(); + let edition = sema.attach_first_edition(file_id).edition(sema.db); + let Some(krate) = sema.first_crate(file_id) else { + return Default::default(); + }; + let display_target = krate.to_display_target(sema.db); + let hover_config = HoverConfig { + links_in_hover: true, + memory_layout: None, + documentation: true, + keywords: true, + format: crate::HoverDocFormat::Markdown, + max_trait_assoc_items_count: None, + max_fields_count: Some(5), + max_enum_variants_count: Some(5), + max_subst_ty_len: SubstTyLen::Unlimited, + show_drop_glue: true, + ra_fixture: RaFixtureConfig::default(), + }; + let mut result: FxHashMap = Default::default(); + result.insert(file_id, StaticIndexedFile { file_id, folds, tokens: vec![] }); - let mut add_token = |def: Definition<'a>, range: TextRange, scope_node: &SyntaxNode| { - let id = if let Some(it) = self.def_map.get(&def) { - *it - } else { + let mut add_token = + |def: Definition<'a>, file_id: FileId, range: TextRange, scope_node: &SyntaxNode| { + let id = *def_map.entry(def).or_insert_with(|| { let nav = def.try_to_nav(&sema).map(UpmappingResult::call_site); - let it = self.tokens.insert(TokenStaticData { - documentation: documentation_for_definition(&sema, def, scope_node), - hover: Some(hover_for_definition( + let hover = if with_hover { + Some(hover_for_definition( &sema, file_id, def, @@ -213,7 +227,13 @@ impl<'a> StaticIndex<'a> { &hover_config, edition, display_target, - )), + )) + } else { + None + }; + token_store.insert(TokenStaticData { + documentation: documentation_for_definition(&sema, def, scope_node), + hover, definition: nav.as_ref().map(|it| FileRange { file_id: it.file_id, range: it.focus_or_full_range(), @@ -223,17 +243,13 @@ impl<'a> StaticIndex<'a> { range: definition_range_excluding_trivia(&sema, it.file_id, it.full_range), }), references: vec![], - moniker: current_crate.and_then(|cc| def_to_moniker(self.db, def, cc)), - display_name: def - .name(self.db) - .map(|name| name.display(self.db, edition).to_string()), - signature: Some(def.label(self.db, display_target)), - kind: def_to_kind(self.db, def), - }); - self.def_map.insert(def, it); - it - }; - let token = self.tokens.get_mut(id).unwrap(); + moniker: current_crate.and_then(|cc| def_to_moniker(db, def, cc)), + display_name: def.name(db).map(|name| name.display(db, edition).to_string()), + signature: Some(def.label(db, display_target)), + kind: def_to_kind(db, def), + }) + }); + let mut token = token_store.get_mut(id).unwrap(); token.references.push(ReferenceData { range: FileRange { range, file_id }, is_definition: match def.try_to_nav(&sema).map(UpmappingResult::call_site) { @@ -241,69 +257,168 @@ impl<'a> StaticIndex<'a> { None => false, }, }); - result.tokens.push((range, id)); + result + .entry(file_id) + .or_insert_with(|| StaticIndexedFile { file_id, folds: vec![], tokens: vec![] }) + .tokens + .push((range, id)); }; - if let Some(module) = sema.file_to_module_def(file_id) { - let def = Definition::Module(module); - let range = root.text_range(); - add_token(def, range, &root); - } + if let Some(module) = sema.file_to_module_def(file_id) { + let def = Definition::Module(module); + let range = root.text_range(); + add_token(def, file_id, range, &root); + } + + // The loop below will traverse all macro expansions, but only record tokens from the current file and include! calls. + #[derive(PartialEq, Eq, Clone, Copy)] + enum RootKind { + OnlyTraverse, + RecordTokens, + } + + let mut roots = VecDeque::from([(root, RootKind::RecordTokens)]); + while let Some((root, root_kind)) = roots.pop_front() { + for node_or_token in root.descendants_with_tokens() { + match node_or_token { + NodeOrToken::Token(token) => { + if root_kind != RootKind::RecordTokens { + continue; + } + + let Some(node) = token.parent() else { + continue; + }; + let Some(defs) = hir::attach_db(db, || get_definitions(&sema, token.clone())) + else { + continue; + }; + + let file_range = InFile::new(sema.hir_file_for(&node), token.text_range()) + .original_node_file_range_rooted(db); - for token in tokens { - let range = token.text_range(); - let node = token.parent().unwrap(); - match hir::attach_db(self.db, || get_definitions(&sema, token.clone())) { - Some(defs) => { for (def, _) in defs { - add_token(def, range, &node); + let range = file_range.range; + let file_id = file_range.file_id.file_id(db); + add_token(def, file_id, range, &node); } } - None => continue, - }; + + NodeOrToken::Node(node) => { + let Some(macro_call) = ast::MacroCall::cast(node) else { + continue; + }; + let Some(macro_call) = sema.to_def(¯o_call) else { + continue; + }; + + let expansion = sema.parse_or_expand(macro_call.into()); + let root_kind = if macro_call.is_include_macro(db) { + RootKind::RecordTokens + } else { + RootKind::OnlyTraverse + }; + roots.push_front((expansion, root_kind)); + } + } } - self.files.push(result); } + result +} + +impl StaticIndex { pub fn compute( - analysis: &'a Analysis, + analysis: &Analysis, vendored_libs_config: VendoredLibrariesConfig<'_>, - ) -> StaticIndex<'a> { + num_threads: usize, + with_folds: bool, + with_hover: bool, + ) -> Self { let db = &analysis.db; - hir::attach_db(db, || { - let work = all_modules(db).into_iter().filter(|module| { - let file_id = module.definition_source_file_id(db).original_file(db); - let source_root = - db.file_source_root(file_id.file_id(&analysis.db)).source_root_id(db); - let source_root = db.source_root(source_root).source_root(db); - let is_vendored = match vendored_libs_config { - VendoredLibrariesConfig::Included { workspace_root } => source_root - .path_for_file(&file_id.file_id(&analysis.db)) - .is_some_and(|module_path| module_path.starts_with(workspace_root)), - VendoredLibrariesConfig::Excluded => false, - }; - !source_root.is_library || is_vendored + let files_to_index = { + let mut files_to_index: Vec<_> = hir::attach_db(db, || { + let modules = all_modules(db).into_iter(); + + let modules_to_index = modules.filter(|module| { + let file_id = module.definition_source_file_id(db).original_file(db); + let source_root = db.file_source_root(file_id.file_id(db)).source_root_id(db); + let source_root = db.source_root(source_root).source_root(db); + let is_vendored = match vendored_libs_config { + VendoredLibrariesConfig::Included { workspace_root } => source_root + .path_for_file(&file_id.file_id(db)) + .is_some_and(|module_path| module_path.starts_with(workspace_root)), + VendoredLibrariesConfig::Excluded => false, + }; + + !source_root.is_library || is_vendored + }); + + let files_to_index = modules_to_index.map(|module| { + module.definition_source_file_id(db).original_file(db).file_id(db) + }); + + files_to_index.collect() }); - let mut this = StaticIndex { - files: vec![], - tokens: Default::default(), - analysis, - db, - def_map: Default::default(), - }; - let mut visited_files = FxHashSet::default(); - for module in work { - let file_id = - module.definition_source_file_id(db).original_file(db).file_id(&analysis.db); - if visited_files.contains(&file_id) { - continue; - } - this.add_file(file_id); - visited_files.insert(file_id); + files_to_index.sort(); + files_to_index.dedup(); + files_to_index + }; + + // Because Analysis is not Sync, each thread needs to get exclusive access to its own Analysis clone. + // The Definitions which are used as keys in def_map inherit the lifetime of these Analysis clones, so analyses should live longer than def_map. + // NOTE this assumes that Definitions yielded from multiple untouched Analysis clones still compare equal. + let mut analyses: Vec<_> = (0..num_threads).map(|_| analysis.clone()).collect(); + + let def_map = Default::default(); + let tokens = Default::default(); + let files = std::thread::scope(|s| { + let threads: Vec<_> = analyses + .iter_mut() + .enumerate() + .map(|(thread_index, analysis)| { + let files_to_index = files_to_index + .iter() + .copied() + .enumerate() + .filter(move |(index, _)| index % num_threads == thread_index) + .map(|(_, file_id)| file_id); + + s.spawn(|| { + let analysis = analysis; + hir::attach_db(&analysis.db, || { + files_to_index + .into_iter() + .flat_map(|file_id| { + index_file( + analysis, &tokens, &def_map, file_id, with_folds, + with_hover, + ) + }) + .collect::>() + }) + }) + }) + .collect(); + + let files_with_duplicates = + threads.into_iter().flat_map(|join_handle| join_handle.join().unwrap()); + + let mut result = FxHashMap::default(); + for (file_id, mut file) in files_with_duplicates { + result + .entry(file_id) + .and_modify(|existing_file: &mut StaticIndexedFile| { + existing_file.tokens.extend(std::mem::take(&mut file.tokens)) + }) + .or_insert(file); } - this - }) + + result.into_values().collect() + }); + + StaticIndex { files, tokens } } } @@ -373,7 +488,7 @@ mod tests { vendored_libs_config: VendoredLibrariesConfig<'_>, ) { let (analysis, ranges) = fixture::annotations_without_marker(ra_fixture); - let s = StaticIndex::compute(&analysis, vendored_libs_config); + let s = StaticIndex::compute(&analysis, vendored_libs_config, 1, false, false); let mut range_set: FxHashSet<_> = ranges.iter().map(|it| it.0).collect(); for f in s.files { for (range, _) in f.tokens { @@ -399,7 +514,7 @@ mod tests { vendored_libs_config: VendoredLibrariesConfig<'_>, ) { let (analysis, ranges) = fixture::annotations_without_marker(ra_fixture); - let s = StaticIndex::compute(&analysis, vendored_libs_config); + let s = StaticIndex::compute(&analysis, vendored_libs_config, 1, false, false); let mut range_set: FxHashSet<_> = ranges.iter().map(|it| it.0).collect(); for (_, t) in s.tokens.iter() { if let Some(t) = t.definition { @@ -424,7 +539,7 @@ mod tests { vendored_libs_config: VendoredLibrariesConfig<'_>, ) { let (analysis, ranges) = fixture::annotations_without_marker(ra_fixture); - let s = StaticIndex::compute(&analysis, vendored_libs_config); + let s = StaticIndex::compute(&analysis, vendored_libs_config, 1, false, false); let mut range_set: FxHashMap<_, i32> = ranges.iter().map(|it| (it.0, 0)).collect(); // Make sure that all references have at least one range. We use a HashMap instead of a diff --git a/crates/rust-analyzer/src/cli/lsif.rs b/crates/rust-analyzer/src/cli/lsif.rs index 8a1d5f336dd7..bde1fc376a94 100644 --- a/crates/rust-analyzer/src/cli/lsif.rs +++ b/crates/rust-analyzer/src/cli/lsif.rs @@ -317,7 +317,7 @@ impl flags::Lsif { VendoredLibrariesConfig::Included { workspace_root: &path.clone().into() } }; - let si = StaticIndex::compute(&analysis, vendored_libs_config); + let si = StaticIndex::compute(&analysis, vendored_libs_config, 1, true, true); let mut lsif = LsifManager::new(&analysis, db, &vfs, out); lsif.add_vertex(lsif::Vertex::MetaData(lsif::MetaData { @@ -333,7 +333,22 @@ impl flags::Lsif { for file in si.files { lsif.add_file(file); } - for (id, token) in si.tokens.iter() { + + // The output order depends on the iteration order, make that order somewhat stable to avoid making lsif_contains_generated_constant flaky + let tokens = { + let mut tokens: Vec<_> = si.tokens.iter().collect(); + tokens.sort_by_key(|(_, token)| { + ( + token.references[0].range.file_id, + token.references[0].range.range.start(), + token.references[0].range.range.end(), + token.kind, + ) + }); + tokens + }; + + for (id, token) in tokens { lsif.add_token(id, token); } eprintln!("Generating LSIF finished in {:?}", now.elapsed()); diff --git a/crates/rust-analyzer/src/cli/scip.rs b/crates/rust-analyzer/src/cli/scip.rs index 3d904e7148e2..a96ef404d241 100644 --- a/crates/rust-analyzer/src/cli/scip.rs +++ b/crates/rust-analyzer/src/cli/scip.rs @@ -22,7 +22,8 @@ use crate::{ impl flags::Scip { pub fn run(self) -> anyhow::Result<()> { - eprintln!("Generating SCIP start..."); + let num_threads = self.num_threads.unwrap_or_else(num_cpus::get_physical); + eprintln!("Generating SCIP start with {num_threads} threads..."); let now = Instant::now(); let no_progress = &|s| eprintln!("rust-analyzer: Loading {s}"); @@ -52,7 +53,7 @@ impl flags::Scip { load_out_dirs_from_check: true, with_proc_macro_server: ProcMacroServerChoice::Sysroot, prefill_caches: true, - num_worker_threads: self.num_threads.unwrap_or_else(num_cpus::get_physical), + num_worker_threads: num_threads, proc_macro_processes: config.proc_macro_num_processes(), }; let cargo_config = config.cargo(None); @@ -72,7 +73,7 @@ impl flags::Scip { VendoredLibrariesConfig::Included { workspace_root: &root.clone().into() } }; - let si = StaticIndex::compute(&analysis, vendored_libs_config); + let si = StaticIndex::compute(&analysis, vendored_libs_config, num_threads, false, false); let metadata = scip_types::Metadata { version: scip_types::ProtocolVersion::UnspecifiedProtocolVersion.into(), @@ -145,7 +146,7 @@ impl flags::Scip { let token = si.tokens.get(id).unwrap(); let Some(TokenSymbols { symbol, enclosing_symbol, is_inherent_impl }) = - symbol_generator.token_symbols(id, token) + symbol_generator.token_symbols(id, token.value()) else { // token did not have a moniker, so there is no reasonable occurrence to emit // see ide::moniker::def_to_moniker @@ -171,7 +172,7 @@ impl flags::Scip { symbols.push(compute_symbol_info( symbol.clone(), enclosing_symbol, - token, + token.value(), )); } } @@ -253,7 +254,7 @@ impl flags::Scip { } let TokenSymbols { symbol, enclosing_symbol, .. } = symbol_generator - .token_symbols(id, token) + .token_symbols(id, token.value()) .expect("To have been referenced, the symbol must be in the cache."); record_error_if_symbol_already_used( @@ -263,7 +264,11 @@ impl flags::Scip { &line_index, text_range, ); - external_symbols.push(compute_symbol_info(symbol.clone(), enclosing_symbol, token)); + external_symbols.push(compute_symbol_info( + symbol.clone(), + enclosing_symbol, + token.value(), + )); } let index = scip_types::Index { @@ -535,7 +540,7 @@ mod test { /// If expected == "", then assert that there are no symbols (this is basically local symbol) #[track_caller] - fn check_symbol(#[rust_analyzer::rust_fixture] ra_fixture: &str, expected: &str) { + fn check_symbols(#[rust_analyzer::rust_fixture] ra_fixture: &str, expected: &[&str]) { let (host, position) = position(ra_fixture); let analysis = host.analysis(); @@ -544,52 +549,46 @@ mod test { VendoredLibrariesConfig::Included { workspace_root: &VfsPath::new_virtual_path("/workspace".to_owned()), }, + 1, + false, + false, ); let FilePosition { file_id, offset } = position; - let mut found_symbol = None; - for file in &si.files { - if file.file_id != file_id { - continue; - } - for &(range, id) in &file.tokens { - // check if cursor is within token, ignoring token for the module defined by the file (whose range is the whole file) - if range.start() != TextSize::from(0) && range.contains(offset - TextSize::from(1)) - { - let token = si.tokens.get(id).unwrap(); - found_symbol = match token.moniker.as_ref() { - None => None, - Some(MonikerResult::Moniker(moniker)) => { - Some(scip::symbol::format_symbol(moniker_to_symbol(moniker))) - } - Some(MonikerResult::Local { enclosing_moniker: Some(moniker) }) => { - Some(format!( - "local enclosed by {}", - scip::symbol::format_symbol(moniker_to_symbol(moniker)) - )) - } - Some(MonikerResult::Local { enclosing_moniker: None }) => { - Some("unenclosed local".to_owned()) - } - }; - break; - } - } - } + let tokens_at_position = si + .files + .into_iter() + .filter(|file| file.file_id == file_id) + .flat_map(|file| file.tokens) + .filter(|(range, _token_id)| { + range.start() != TextSize::from(0) && range.contains(offset - TextSize::from(1)) + }) + .map(|(_range, token_id)| si.tokens.get(token_id).unwrap()); - if expected.is_empty() { - assert!(found_symbol.is_none(), "must have no symbols {found_symbol:?}"); - return; - } + let mut symbols_at_position: Vec<_> = tokens_at_position + .filter_map(|token| match token.moniker.as_ref() { + None => None, + Some(MonikerResult::Moniker(moniker)) => { + Some(scip::symbol::format_symbol(moniker_to_symbol(moniker))) + } + Some(MonikerResult::Local { enclosing_moniker: Some(moniker) }) => Some(format!( + "local enclosed by {}", + scip::symbol::format_symbol(moniker_to_symbol(moniker)) + )), + Some(MonikerResult::Local { enclosing_moniker: None }) => { + Some("unenclosed local".to_owned()) + } + }) + .collect(); - assert!(found_symbol.is_some(), "must have one symbol {found_symbol:?}"); - assert_eq!(found_symbol.unwrap(), expected); + symbols_at_position.sort_unstable(); + assert_eq!(symbols_at_position, expected); } #[test] fn basic() { - check_symbol( + check_symbols( r#" //- /workspace/lib.rs crate:main deps:foo use foo::example_mod::func; @@ -601,13 +600,13 @@ pub mod example_mod { pub fn func() {} } "#, - "rust-analyzer cargo foo 0.1.0 example_mod/func().", + &["rust-analyzer cargo foo 0.1.0 example_mod/func()."], ); } #[test] fn operator_overload() { - check_symbol( + check_symbols( r#" //- minicore: add //- /workspace/lib.rs crate:main @@ -624,13 +623,13 @@ fn main() { s +=$0 S; } "#, - "rust-analyzer cargo main . impl#[S][`AddAssign`]add_assign().", + &["rust-analyzer cargo main . impl#[S][`AddAssign`]add_assign()."], ); } #[test] fn symbol_for_trait() { - check_symbol( + check_symbols( r#" //- /foo/lib.rs crate:foo@0.1.0,https://a.b/foo.git library pub mod module { @@ -639,13 +638,13 @@ pub mod module { } } "#, - "rust-analyzer cargo foo 0.1.0 module/MyTrait#func().", + &["rust-analyzer cargo foo 0.1.0 module/MyTrait#func()."], ); } #[test] fn symbol_for_trait_alias() { - check_symbol( + check_symbols( r#" //- /foo/lib.rs crate:foo@0.1.0,https://a.b/foo.git library #![feature(trait_alias)] @@ -654,13 +653,13 @@ pub mod module { pub trait MyTraitAlias$0 = MyTrait; } "#, - "rust-analyzer cargo foo 0.1.0 module/MyTraitAlias#", + &["rust-analyzer cargo foo 0.1.0 module/MyTraitAlias#"], ); } #[test] fn symbol_for_trait_constant() { - check_symbol( + check_symbols( r#" //- /foo/lib.rs crate:foo@0.1.0,https://a.b/foo.git library pub mod module { @@ -669,13 +668,13 @@ pub mod module { } } "#, - "rust-analyzer cargo foo 0.1.0 module/MyTrait#MY_CONST.", + &["rust-analyzer cargo foo 0.1.0 module/MyTrait#MY_CONST."], ); } #[test] fn symbol_for_trait_type() { - check_symbol( + check_symbols( r#" //- /foo/lib.rs crate:foo@0.1.0,https://a.b/foo.git library pub mod module { @@ -684,13 +683,13 @@ pub mod module { } } "#, - "rust-analyzer cargo foo 0.1.0 module/MyTrait#MyType#", + &["rust-analyzer cargo foo 0.1.0 module/MyTrait#MyType#"], ); } #[test] fn symbol_for_trait_impl_function() { - check_symbol( + check_symbols( r#" //- /foo/lib.rs crate:foo@0.1.0,https://a.b/foo.git library pub mod module { @@ -705,13 +704,13 @@ pub mod module { } } "#, - "rust-analyzer cargo foo 0.1.0 module/impl#[MyStruct][MyTrait]func().", + &["rust-analyzer cargo foo 0.1.0 module/impl#[MyStruct][MyTrait]func()."], ); } #[test] fn symbol_for_field() { - check_symbol( + check_symbols( r#" //- /workspace/lib.rs crate:main deps:foo use foo::St; @@ -723,13 +722,13 @@ pub mod module { pub a: i32, } "#, - "rust-analyzer cargo foo 0.1.0 St#a.", + &["rust-analyzer cargo foo 0.1.0 St#a."], ); } #[test] fn symbol_for_param() { - check_symbol( + check_symbols( r#" //- /workspace/lib.rs crate:main deps:foo use foo::example_mod::func; @@ -741,13 +740,13 @@ pub mod example_mod { pub fn func(x$0: usize) {} } "#, - "local enclosed by rust-analyzer cargo foo 0.1.0 example_mod/func().", + &["local enclosed by rust-analyzer cargo foo 0.1.0 example_mod/func()."], ); } #[test] fn symbol_for_closure_param() { - check_symbol( + check_symbols( r#" //- /workspace/lib.rs crate:main deps:foo use foo::example_mod::func; @@ -761,13 +760,13 @@ pub mod example_mod { } } "#, - "local enclosed by rust-analyzer cargo foo 0.1.0 example_mod/func().", + &["local enclosed by rust-analyzer cargo foo 0.1.0 example_mod/func()."], ); } #[test] fn local_symbol_for_local() { - check_symbol( + check_symbols( r#" //- /workspace/lib.rs crate:main deps:foo use foo::module::func; @@ -781,13 +780,13 @@ pub mod example_mod { } } "#, - "local enclosed by rust-analyzer cargo foo 0.1.0 module/func().", + &["local enclosed by rust-analyzer cargo foo 0.1.0 module/func()."], ); } #[test] fn global_symbol_for_pub_struct() { - check_symbol( + check_symbols( r#" //- /workspace/lib.rs crate:main mod foo; @@ -800,13 +799,13 @@ pub mod example_mod { pub i: i32, } "#, - "rust-analyzer cargo main . foo/Bar#", + &["rust-analyzer cargo main . foo/Bar#"], ); } #[test] fn global_symbol_for_pub_struct_reference() { - check_symbol( + check_symbols( r#" //- /workspace/lib.rs crate:main mod foo; @@ -819,32 +818,32 @@ pub mod example_mod { pub i: i32, } "#, - "rust-analyzer cargo main . foo/Bar#", + &["rust-analyzer cargo main . foo/Bar#"], ); } #[test] fn symbol_for_type_alias() { - check_symbol( + check_symbols( r#" //- /workspace/lib.rs crate:main pub type MyTypeAlias$0 = u8; "#, - "rust-analyzer cargo main . MyTypeAlias#", + &["rust-analyzer cargo main . MyTypeAlias#"], ); } // FIXME: This test represents current misbehavior. #[test] fn symbol_for_nested_function() { - check_symbol( + check_symbols( r#" //- /workspace/lib.rs crate:main pub fn func() { pub fn inner_func$0() {} } "#, - "rust-analyzer cargo main . inner_func().", + &["rust-analyzer cargo main . inner_func()."], // FIXME: This should be a local: // "local enclosed by rust-analyzer cargo main . func().", ); @@ -853,14 +852,14 @@ pub mod example_mod { // FIXME: This test represents current misbehavior. #[test] fn symbol_for_struct_in_function() { - check_symbol( + check_symbols( r#" //- /workspace/lib.rs crate:main pub fn func() { struct SomeStruct$0 {} } "#, - "rust-analyzer cargo main . SomeStruct#", + &["rust-analyzer cargo main . SomeStruct#"], // FIXME: This should be a local: // "local enclosed by rust-analyzer cargo main . func().", ); @@ -869,14 +868,14 @@ pub mod example_mod { // FIXME: This test represents current misbehavior. #[test] fn symbol_for_const_in_function() { - check_symbol( + check_symbols( r#" //- /workspace/lib.rs crate:main pub fn func() { const SOME_CONST$0: u32 = 1; } "#, - "rust-analyzer cargo main . SOME_CONST.", + &["rust-analyzer cargo main . SOME_CONST."], // FIXME: This should be a local: // "local enclosed by rust-analyzer cargo main . func().", ); @@ -885,19 +884,80 @@ pub mod example_mod { // FIXME: This test represents current misbehavior. #[test] fn symbol_for_static_in_function() { - check_symbol( + check_symbols( r#" //- /workspace/lib.rs crate:main pub fn func() { static SOME_STATIC$0: u32 = 1; } "#, - "rust-analyzer cargo main . SOME_STATIC.", + &["rust-analyzer cargo main . SOME_STATIC."], // FIXME: This should be a local: // "local enclosed by rust-analyzer cargo main . func().", ); } + #[test] + fn include_macro() { + check_symbols( + r#" + //- minicore: include + //- /workspace/lib.rs crate:main + use core::include; + + include!("included.rs"); + + //- /workspace/included.rs + struct Included$0; + "#, + &["rust-analyzer cargo main . Included#"], + ); + } + + #[test] + fn include_macro_indirect() { + check_symbols( + r#" + //- minicore: include + //- /workspace/lib.rs crate:main + macro_rules! includer { + () => { + include!("include_intermediate.rs"); + }; + } + + includer!(); + + //- /workspace/include_intermediate.rs + include!("included.rs"); + + //- /workspace/included.rs + struct Included$0; + "#, + &["rust-analyzer cargo main . Included#"], + ); + } + + #[test] + fn include_macro_multiple() { + check_symbols( + r#" + //- minicore: include + //- /workspace/lib.rs crate:main + mod a { + include!("included.rs"); + } + mod b { + include!("included.rs"); + } + + //- /workspace/included.rs + struct Included$0; + "#, + &["rust-analyzer cargo main . a/Included#", "rust-analyzer cargo main . b/Included#"], + ); + } + #[test] fn documentation_matches_doc_comment() { let s = "/// foo\nfn bar() {}"; @@ -912,6 +972,9 @@ pub mod example_mod { VendoredLibrariesConfig::Included { workspace_root: &VfsPath::new_virtual_path("/workspace".to_owned()), }, + 1, + false, + false, ); let file = si.files.first().unwrap(); @@ -935,6 +998,9 @@ pub mod example_mod { VendoredLibrariesConfig::Included { workspace_root: &VfsPath::new_virtual_path("/workspace".to_owned()), }, + 1, + false, + false, ); let file = si.files.first().unwrap(); @@ -963,6 +1029,9 @@ pub mod example_mod { VendoredLibrariesConfig::Included { workspace_root: &VfsPath::new_virtual_path("/workspace".to_owned()), }, + 1, + false, + false, ); let file = si.files.first().unwrap(); @@ -995,6 +1064,9 @@ pub mod example_mod { VendoredLibrariesConfig::Included { workspace_root: &VfsPath::new_virtual_path("/workspace".to_owned()), }, + 1, + false, + false, ); let file = si.files.first().unwrap();