diff --git a/src/tools/rust-analyzer/Cargo.lock b/src/tools/rust-analyzer/Cargo.lock index 55e5bdc138d9..539f8cf1b933 100644 --- a/src/tools/rust-analyzer/Cargo.lock +++ b/src/tools/rust-analyzer/Cargo.lock @@ -930,6 +930,7 @@ dependencies = [ "ide-diagnostics", "ide-ssr", "itertools", + "macros", "nohash-hasher", "oorandom", "profile", @@ -976,6 +977,7 @@ dependencies = [ "hir", "ide-db", "itertools", + "macros", "smallvec", "stdx", "syntax", @@ -1000,6 +1002,7 @@ dependencies = [ "indexmap", "itertools", "line-index 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)", + "macros", "memchr", "nohash-hasher", "parser", @@ -1009,6 +1012,7 @@ dependencies = [ "rustc-hash 2.1.1", "salsa", "salsa-macros", + "smallvec", "span", "stdx", "syntax", diff --git a/src/tools/rust-analyzer/crates/ide-completion/Cargo.toml b/src/tools/rust-analyzer/crates/ide-completion/Cargo.toml index 9bad21fc8e90..277d5dfa495c 100644 --- a/src/tools/rust-analyzer/crates/ide-completion/Cargo.toml +++ b/src/tools/rust-analyzer/crates/ide-completion/Cargo.toml @@ -28,6 +28,7 @@ syntax.workspace = true # completions crate should depend only on the top-level `hir` package. if you need # something from some `hir-xxx` subpackage, reexport the API via `hir`. hir.workspace = true +macros.workspace = true [dev-dependencies] expect-test = "1.5.1" diff --git a/src/tools/rust-analyzer/crates/ide-completion/src/completions.rs b/src/tools/rust-analyzer/crates/ide-completion/src/completions.rs index b822f53d7b7b..ed58e862d437 100644 --- a/src/tools/rust-analyzer/crates/ide-completion/src/completions.rs +++ b/src/tools/rust-analyzer/crates/ide-completion/src/completions.rs @@ -16,6 +16,7 @@ pub(crate) mod lifetime; pub(crate) mod mod_; pub(crate) mod pattern; pub(crate) mod postfix; +pub(crate) mod ra_fixture; pub(crate) mod record; pub(crate) mod snippet; pub(crate) mod r#type; @@ -74,6 +75,10 @@ impl Completions { self.buf.push(item) } + fn add_many(&mut self, items: impl IntoIterator) { + self.buf.extend(items) + } + fn add_opt(&mut self, item: Option) { if let Some(item) = item { self.buf.push(item) diff --git a/src/tools/rust-analyzer/crates/ide-completion/src/completions/ra_fixture.rs b/src/tools/rust-analyzer/crates/ide-completion/src/completions/ra_fixture.rs new file mode 100644 index 000000000000..b44c90757f68 --- /dev/null +++ b/src/tools/rust-analyzer/crates/ide-completion/src/completions/ra_fixture.rs @@ -0,0 +1,113 @@ +//! Injected completions for `#[rust_analyzer::rust_fixture]`. + +use hir::FilePositionWrapper; +use ide_db::{ + impl_empty_upmap_from_ra_fixture, + ra_fixture::{RaFixtureAnalysis, UpmapFromRaFixture}, +}; +use syntax::ast; + +use crate::{ + CompletionItemKind, CompletionItemRefMode, CompletionRelevance, completions::Completions, + context::CompletionContext, item::CompletionItemLabel, +}; + +pub(crate) fn complete_ra_fixture( + acc: &mut Completions, + ctx: &CompletionContext<'_>, + original: &ast::String, + expanded: &ast::String, +) -> Option<()> { + let analysis = RaFixtureAnalysis::analyze_ra_fixture( + &ctx.sema, + original.clone(), + expanded, + ctx.config.minicore, + &mut |_| {}, + )?; + let (virtual_file_id, virtual_offset) = analysis.map_offset_down(ctx.position.offset)?; + let completions = hir::attach_db_allow_change(&analysis.db, || { + crate::completions( + &analysis.db, + ctx.config, + FilePositionWrapper { file_id: virtual_file_id, offset: virtual_offset }, + ctx.trigger_character, + ) + })?; + let completions = + completions.upmap_from_ra_fixture(&analysis, virtual_file_id, ctx.position.file_id).ok()?; + acc.add_many(completions); + Some(()) +} + +impl_empty_upmap_from_ra_fixture!( + CompletionItemLabel, + CompletionItemKind, + CompletionRelevance, + CompletionItemRefMode, +); + +#[cfg(test)] +mod tests { + use expect_test::expect; + + use crate::tests::check; + + #[test] + fn it_works() { + check( + r##" +fn fixture(#[rust_analyzer::rust_fixture] ra_fixture: &str) {} + +fn foo() { + fixture(r#" +fn complete_me() {} + +fn baz() { + let foo_bar_baz = 123; + f$0 +} + "#); +} + "##, + expect![[r#" + fn baz() fn() + fn complete_me() fn() + lc foo_bar_baz i32 + bt u32 u32 + kw async + kw const + kw crate:: + kw enum + kw extern + kw false + kw fn + kw for + kw if + kw if let + kw impl + kw impl for + kw let + kw letm + kw loop + kw match + kw mod + kw return + kw self:: + kw static + kw struct + kw trait + kw true + kw type + kw union + kw unsafe + kw use + kw while + kw while let + sn macro_rules + sn pd + sn ppd + "#]], + ); + } +} diff --git a/src/tools/rust-analyzer/crates/ide-completion/src/config.rs b/src/tools/rust-analyzer/crates/ide-completion/src/config.rs index b7367cb62f09..5623257a2792 100644 --- a/src/tools/rust-analyzer/crates/ide-completion/src/config.rs +++ b/src/tools/rust-analyzer/crates/ide-completion/src/config.rs @@ -6,13 +6,13 @@ use hir::FindPathConfig; use ide_db::{ - SnippetCap, + MiniCore, SnippetCap, imports::{import_assets::ImportPathConfig, insert_use::InsertUseConfig}, }; use crate::{CompletionFieldsToResolve, snippet::Snippet}; -#[derive(Clone, Debug, PartialEq, Eq)] +#[derive(Clone, Debug)] pub struct CompletionConfig<'a> { pub enable_postfix_completions: bool, pub enable_imports_on_the_fly: bool, @@ -35,6 +35,7 @@ pub struct CompletionConfig<'a> { pub fields_to_resolve: CompletionFieldsToResolve, pub exclude_flyimport: Vec<(String, AutoImportExclusionType)>, pub exclude_traits: &'a [String], + pub minicore: MiniCore<'a>, } #[derive(Copy, Clone, Debug, PartialEq, Eq, Hash)] diff --git a/src/tools/rust-analyzer/crates/ide-completion/src/context.rs b/src/tools/rust-analyzer/crates/ide-completion/src/context.rs index 4032329ac658..fc2cc3b796ec 100644 --- a/src/tools/rust-analyzer/crates/ide-completion/src/context.rs +++ b/src/tools/rust-analyzer/crates/ide-completion/src/context.rs @@ -440,6 +440,7 @@ pub(crate) struct CompletionContext<'a> { pub(crate) config: &'a CompletionConfig<'a>, pub(crate) position: FilePosition, + pub(crate) trigger_character: Option, /// The token before the cursor, in the original file. pub(crate) original_token: SyntaxToken, /// The token before the cursor, in the macro-expanded file. @@ -703,6 +704,7 @@ impl<'db> CompletionContext<'db> { db: &'db RootDatabase, position @ FilePosition { file_id, offset }: FilePosition, config: &'db CompletionConfig<'db>, + trigger_character: Option, ) -> Option<(CompletionContext<'db>, CompletionAnalysis<'db>)> { let _p = tracing::info_span!("CompletionContext::new").entered(); let sema = Semantics::new(db); @@ -871,6 +873,7 @@ impl<'db> CompletionContext<'db> { db, config, position, + trigger_character, original_token, token, krate, diff --git a/src/tools/rust-analyzer/crates/ide-completion/src/context/tests.rs b/src/tools/rust-analyzer/crates/ide-completion/src/context/tests.rs index e798f3b23af4..51d28bd4ff98 100644 --- a/src/tools/rust-analyzer/crates/ide-completion/src/context/tests.rs +++ b/src/tools/rust-analyzer/crates/ide-completion/src/context/tests.rs @@ -10,7 +10,7 @@ fn check_expected_type_and_name(#[rust_analyzer::rust_fixture] ra_fixture: &str, let (db, pos) = position(ra_fixture); let config = TEST_CONFIG; let (completion_context, _analysis) = - hir::attach_db(&db, || CompletionContext::new(&db, pos, &config).unwrap()); + hir::attach_db(&db, || CompletionContext::new(&db, pos, &config, None).unwrap()); let ty = completion_context .expected_type diff --git a/src/tools/rust-analyzer/crates/ide-completion/src/item.rs b/src/tools/rust-analyzer/crates/ide-completion/src/item.rs index 5fb9dc93c93d..303c71230d60 100644 --- a/src/tools/rust-analyzer/crates/ide-completion/src/item.rs +++ b/src/tools/rust-analyzer/crates/ide-completion/src/item.rs @@ -9,6 +9,7 @@ use ide_db::{ imports::import_assets::LocatedImport, }; use itertools::Itertools; +use macros::UpmapFromRaFixture; use smallvec::SmallVec; use stdx::{format_to, impl_from, never}; use syntax::{Edition, SmolStr, TextRange, TextSize, format_smolstr}; @@ -23,7 +24,7 @@ use crate::{ /// /// It is basically a POD with various properties. To construct a [`CompletionItem`], /// use [`Builder::new`] method and the [`Builder`] struct. -#[derive(Clone)] +#[derive(Clone, UpmapFromRaFixture)] #[non_exhaustive] pub struct CompletionItem { /// Label in the completion pop up which identifies completion. diff --git a/src/tools/rust-analyzer/crates/ide-completion/src/lib.rs b/src/tools/rust-analyzer/crates/ide-completion/src/lib.rs index a70a1138d2f4..8a0aaf3f0cc2 100644 --- a/src/tools/rust-analyzer/crates/ide-completion/src/lib.rs +++ b/src/tools/rust-analyzer/crates/ide-completion/src/lib.rs @@ -187,7 +187,7 @@ pub fn completions( position: FilePosition, trigger_character: Option, ) -> Option> { - let (ctx, analysis) = &CompletionContext::new(db, position, config)?; + let (ctx, analysis) = &CompletionContext::new(db, position, config, trigger_character)?; let mut completions = Completions::default(); // prevent `(` from triggering unwanted completion noise @@ -241,6 +241,7 @@ pub fn completions( completions::extern_abi::complete_extern_abi(acc, ctx, expanded); completions::format_string::format_string(acc, ctx, original, expanded); completions::env_vars::complete_cargo_env_vars(acc, ctx, original, expanded); + completions::ra_fixture::complete_ra_fixture(acc, ctx, original, expanded); } CompletionAnalysis::UnexpandedAttrTT { colon_prefix, diff --git a/src/tools/rust-analyzer/crates/ide-completion/src/tests.rs b/src/tools/rust-analyzer/crates/ide-completion/src/tests.rs index ec9cd9fdf378..b32a89545726 100644 --- a/src/tools/rust-analyzer/crates/ide-completion/src/tests.rs +++ b/src/tools/rust-analyzer/crates/ide-completion/src/tests.rs @@ -29,7 +29,7 @@ use expect_test::Expect; use hir::db::HirDatabase; use hir::{PrefixKind, setup_tracing}; use ide_db::{ - FilePosition, RootDatabase, SnippetCap, + FilePosition, MiniCore, RootDatabase, SnippetCap, imports::insert_use::{ImportGranularity, InsertUseConfig}, }; use itertools::Itertools; @@ -90,6 +90,7 @@ pub(crate) const TEST_CONFIG: CompletionConfig<'_> = CompletionConfig { exclude_traits: &[], enable_auto_await: true, enable_auto_iter: true, + minicore: MiniCore::default(), }; pub(crate) fn completion_list(#[rust_analyzer::rust_fixture] ra_fixture: &str) -> String { diff --git a/src/tools/rust-analyzer/crates/ide-completion/src/tests/flyimport.rs b/src/tools/rust-analyzer/crates/ide-completion/src/tests/flyimport.rs index 2d3ebad9340c..0cd42089b487 100644 --- a/src/tools/rust-analyzer/crates/ide-completion/src/tests/flyimport.rs +++ b/src/tools/rust-analyzer/crates/ide-completion/src/tests/flyimport.rs @@ -16,7 +16,8 @@ fn check_with_config( expect: Expect, ) { let (db, position) = crate::tests::position(ra_fixture); - let (ctx, analysis) = crate::context::CompletionContext::new(&db, position, &config).unwrap(); + let (ctx, analysis) = + crate::context::CompletionContext::new(&db, position, &config, None).unwrap(); let mut acc = crate::completions::Completions::default(); hir::attach_db(ctx.db, || { diff --git a/src/tools/rust-analyzer/crates/ide-db/Cargo.toml b/src/tools/rust-analyzer/crates/ide-db/Cargo.toml index e065adb0f0ba..b7148160182c 100644 --- a/src/tools/rust-analyzer/crates/ide-db/Cargo.toml +++ b/src/tools/rust-analyzer/crates/ide-db/Cargo.toml @@ -30,6 +30,7 @@ query-group.workspace = true triomphe.workspace = true nohash-hasher.workspace = true bitflags.workspace = true +smallvec.workspace = true # local deps base-db.workspace = true @@ -42,15 +43,15 @@ vfs.workspace = true # ide should depend only on the top-level `hir` package. if you need # something from some `hir-xxx` subpackage, reexport the API via `hir`. hir.workspace = true +macros.workspace = true + +test-utils.workspace = true +test-fixture.workspace = true line-index.workspace = true [dev-dependencies] expect-test = "1.5.1" -# local deps -test-utils.workspace = true -test-fixture.workspace = true - [lints] workspace = true diff --git a/src/tools/rust-analyzer/crates/ide-db/src/lib.rs b/src/tools/rust-analyzer/crates/ide-db/src/lib.rs index 44bccd86d870..7efa97be5573 100644 --- a/src/tools/rust-analyzer/crates/ide-db/src/lib.rs +++ b/src/tools/rust-analyzer/crates/ide-db/src/lib.rs @@ -2,6 +2,8 @@ //! //! It is mainly a `HirDatabase` for semantic analysis, plus a `SymbolsDatabase`, for fuzzy search. +extern crate self as ide_db; + mod apply_change; pub mod active_parameter; @@ -14,6 +16,8 @@ pub mod items_locator; pub mod label; pub mod path_transform; pub mod prime_caches; +pub mod ra_fixture; +pub mod range_mapper; pub mod rename; pub mod rust_doc; pub mod search; @@ -364,3 +368,25 @@ pub enum Severity { WeakWarning, Allow, } + +#[derive(Debug, Clone, Copy)] +pub struct MiniCore<'a>(&'a str); + +impl<'a> MiniCore<'a> { + #[inline] + pub fn new(minicore: &'a str) -> Self { + Self(minicore) + } + + #[inline] + pub const fn default() -> Self { + Self(test_utils::MiniCore::RAW_SOURCE) + } +} + +impl<'a> Default for MiniCore<'a> { + #[inline] + fn default() -> Self { + Self::default() + } +} diff --git a/src/tools/rust-analyzer/crates/ide-db/src/ra_fixture.rs b/src/tools/rust-analyzer/crates/ide-db/src/ra_fixture.rs new file mode 100644 index 000000000000..1f056a835bc6 --- /dev/null +++ b/src/tools/rust-analyzer/crates/ide-db/src/ra_fixture.rs @@ -0,0 +1,532 @@ +//! Working with the fixtures in r-a tests, and providing IDE services for them. + +use std::hash::{BuildHasher, Hash}; + +use hir::{CfgExpr, FilePositionWrapper, FileRangeWrapper, Semantics}; +use smallvec::SmallVec; +use span::{TextRange, TextSize}; +use syntax::{ + AstToken, SmolStr, + ast::{self, IsString}, +}; + +use crate::{ + MiniCore, RootDatabase, SymbolKind, active_parameter::ActiveParameter, + documentation::Documentation, range_mapper::RangeMapper, search::ReferenceCategory, +}; + +pub use span::FileId; + +impl RootDatabase { + fn from_ra_fixture( + text: &str, + minicore: MiniCore<'_>, + ) -> Result<(RootDatabase, Vec<(FileId, usize)>, Vec), ()> { + // We don't want a mistake in the fixture to crash r-a, so we wrap this in `catch_unwind()`. + std::panic::catch_unwind(|| { + let mut db = RootDatabase::default(); + let fixture = test_fixture::ChangeFixture::parse_with_proc_macros( + &db, + text, + minicore.0, + Vec::new(), + ); + db.apply_change(fixture.change); + let files = fixture + .files + .into_iter() + .zip(fixture.file_lines) + .map(|(file_id, range)| (file_id.file_id(&db), range)) + .collect(); + (db, files, fixture.sysroot_files) + }) + .map_err(|error| { + tracing::error!( + "cannot crate the crate graph: {}\nCrate graph:\n{}\n", + if let Some(&s) = error.downcast_ref::<&'static str>() { + s + } else if let Some(s) = error.downcast_ref::() { + s.as_str() + } else { + "Box" + }, + text, + ); + }) + } +} + +pub struct RaFixtureAnalysis { + pub db: RootDatabase, + tmp_file_ids: Vec<(FileId, usize)>, + line_offsets: Vec, + virtual_file_id_to_line: Vec, + mapper: RangeMapper, + literal: ast::String, + // `minicore` etc.. + sysroot_files: Vec, + combined_len: TextSize, +} + +impl RaFixtureAnalysis { + pub fn analyze_ra_fixture( + sema: &Semantics<'_, RootDatabase>, + literal: ast::String, + expanded: &ast::String, + minicore: MiniCore<'_>, + on_cursor: &mut dyn FnMut(TextRange), + ) -> Option { + if !literal.is_raw() { + return None; + } + + let active_parameter = ActiveParameter::at_token(sema, expanded.syntax().clone())?; + let has_rust_fixture_attr = active_parameter.attrs().is_some_and(|attrs| { + attrs.filter_map(|attr| attr.as_simple_path()).any(|path| { + path.segments() + .zip(["rust_analyzer", "rust_fixture"]) + .all(|(seg, name)| seg.name_ref().map_or(false, |nr| nr.text() == name)) + }) + }); + if !has_rust_fixture_attr { + return None; + } + let value = literal.value().ok()?; + + let mut mapper = RangeMapper::default(); + + // This is used for the `Injector`, to resolve precise location in the string literal, + // which will then be used to resolve precise location in the enclosing file. + let mut offset_with_indent = TextSize::new(0); + // This is used to resolve the location relative to the virtual file into a location + // relative to the indentation-trimmed file which will then (by the `Injector`) used + // to resolve to a location in the actual file. + // Besides indentation, we also skip `$0` cursors for this, since they are not included + // in the virtual files. + let mut offset_without_indent = TextSize::new(0); + + let mut text = &*value; + if let Some(t) = text.strip_prefix('\n') { + offset_with_indent += TextSize::of("\n"); + text = t; + } + // This stores the offsets of each line, **after we remove indentation**. + let mut line_offsets = Vec::new(); + for mut line in text.split_inclusive('\n') { + line_offsets.push(offset_without_indent); + + if line.starts_with("@@") { + // Introducing `//` into a fixture inside fixture causes all sorts of problems, + // so for testing purposes we escape it as `@@` and replace it here. + mapper.add("//", TextRange::at(offset_with_indent, TextSize::of("@@"))); + line = &line["@@".len()..]; + offset_with_indent += TextSize::of("@@"); + offset_without_indent += TextSize::of("@@"); + } + + // Remove indentation to simplify the mapping with fixture (which de-indents). + // Removing indentation shouldn't affect highlighting. + let mut unindented_line = line.trim_start(); + if unindented_line.is_empty() { + // The whole line was whitespaces, but we need the newline. + unindented_line = "\n"; + } + offset_with_indent += TextSize::of(line) - TextSize::of(unindented_line); + + let marker = "$0"; + match unindented_line.find(marker) { + Some(marker_pos) => { + let (before_marker, after_marker) = unindented_line.split_at(marker_pos); + let after_marker = &after_marker[marker.len()..]; + + mapper.add( + before_marker, + TextRange::at(offset_with_indent, TextSize::of(before_marker)), + ); + offset_with_indent += TextSize::of(before_marker); + offset_without_indent += TextSize::of(before_marker); + + if let Some(marker_range) = literal + .map_range_up(TextRange::at(offset_with_indent, TextSize::of(marker))) + { + on_cursor(marker_range); + } + offset_with_indent += TextSize::of(marker); + + mapper.add( + after_marker, + TextRange::at(offset_with_indent, TextSize::of(after_marker)), + ); + offset_with_indent += TextSize::of(after_marker); + offset_without_indent += TextSize::of(after_marker); + } + None => { + mapper.add( + unindented_line, + TextRange::at(offset_with_indent, TextSize::of(unindented_line)), + ); + offset_with_indent += TextSize::of(unindented_line); + offset_without_indent += TextSize::of(unindented_line); + } + } + } + + let combined = mapper.take_text(); + let combined_len = TextSize::of(&combined); + let (analysis, tmp_file_ids, sysroot_files) = + RootDatabase::from_ra_fixture(&combined, minicore).ok()?; + + // We use a `Vec` because we know the `FileId`s will always be close. + let mut virtual_file_id_to_line = Vec::new(); + for &(file_id, line) in &tmp_file_ids { + virtual_file_id_to_line.resize(file_id.index() as usize + 1, usize::MAX); + virtual_file_id_to_line[file_id.index() as usize] = line; + } + + Some(RaFixtureAnalysis { + db: analysis, + tmp_file_ids, + line_offsets, + virtual_file_id_to_line, + mapper, + literal, + sysroot_files, + combined_len, + }) + } + + pub fn files(&self) -> impl Iterator { + self.tmp_file_ids.iter().map(|(file, _)| *file) + } + + /// This returns `None` for minicore or other sysroot files. + fn virtual_file_id_to_line(&self, file_id: FileId) -> Option { + if self.is_sysroot_file(file_id) { + None + } else { + Some(self.virtual_file_id_to_line[file_id.index() as usize]) + } + } + + pub fn map_offset_down(&self, offset: TextSize) -> Option<(FileId, TextSize)> { + let inside_literal_range = self.literal.map_offset_down(offset)?; + let combined_offset = self.mapper.map_offset_down(inside_literal_range)?; + // There is usually a small number of files, so a linear search is smaller and faster. + let (_, &(file_id, file_line)) = + self.tmp_file_ids.iter().enumerate().find(|&(idx, &(_, file_line))| { + let file_start = self.line_offsets[file_line]; + let file_end = self + .tmp_file_ids + .get(idx + 1) + .map(|&(_, next_file_line)| self.line_offsets[next_file_line]) + .unwrap_or_else(|| self.combined_len); + TextRange::new(file_start, file_end).contains(combined_offset) + })?; + let file_line_offset = self.line_offsets[file_line]; + let file_offset = combined_offset - file_line_offset; + Some((file_id, file_offset)) + } + + pub fn map_range_down(&self, range: TextRange) -> Option<(FileId, TextRange)> { + let (start_file_id, start_offset) = self.map_offset_down(range.start())?; + let (end_file_id, end_offset) = self.map_offset_down(range.end())?; + if start_file_id != end_file_id { + None + } else { + Some((start_file_id, TextRange::new(start_offset, end_offset))) + } + } + + pub fn map_range_up( + &self, + virtual_file: FileId, + range: TextRange, + ) -> impl Iterator { + // This could be `None` if the file is empty. + self.virtual_file_id_to_line(virtual_file) + .and_then(|line| self.line_offsets.get(line)) + .into_iter() + .flat_map(move |&tmp_file_offset| { + // Resolve the offset relative to the virtual file to an offset relative to the combined indentation-trimmed file + let range = range + tmp_file_offset; + // Then resolve that to an offset relative to the real file. + self.mapper.map_range_up(range) + }) + // And finally resolve the offset relative to the literal to relative to the file. + .filter_map(|range| self.literal.map_range_up(range)) + } + + pub fn map_offset_up(&self, virtual_file: FileId, offset: TextSize) -> Option { + self.map_range_up(virtual_file, TextRange::empty(offset)).next().map(|range| range.start()) + } + + pub fn is_sysroot_file(&self, file_id: FileId) -> bool { + self.sysroot_files.contains(&file_id) + } +} + +pub trait UpmapFromRaFixture: Sized { + fn upmap_from_ra_fixture( + self, + analysis: &RaFixtureAnalysis, + virtual_file_id: FileId, + real_file_id: FileId, + ) -> Result; +} + +trait IsEmpty { + fn is_empty(&self) -> bool; +} + +impl IsEmpty for Vec { + fn is_empty(&self) -> bool { + self.is_empty() + } +} + +impl IsEmpty for SmallVec<[T; N]> { + fn is_empty(&self) -> bool { + self.is_empty() + } +} + +#[allow(clippy::disallowed_types)] +impl IsEmpty for std::collections::HashMap { + fn is_empty(&self) -> bool { + self.is_empty() + } +} + +fn upmap_collection( + collection: Collection, + analysis: &RaFixtureAnalysis, + virtual_file_id: FileId, + real_file_id: FileId, +) -> Result +where + T: UpmapFromRaFixture, + Collection: IntoIterator + FromIterator + IsEmpty, +{ + if collection.is_empty() { + // The collection was already empty, don't mark it as failing just because of that. + return Ok(collection); + } + let result = collection + .into_iter() + .filter_map(|item| item.upmap_from_ra_fixture(analysis, virtual_file_id, real_file_id).ok()) + .collect::(); + if result.is_empty() { + // The collection was emptied by the upmapping - all items errored, therefore mark it as erroring as well. + Err(()) + } else { + Ok(result) + } +} + +impl UpmapFromRaFixture for Option { + fn upmap_from_ra_fixture( + self, + analysis: &RaFixtureAnalysis, + virtual_file_id: FileId, + real_file_id: FileId, + ) -> Result { + Ok(match self { + Some(it) => Some(it.upmap_from_ra_fixture(analysis, virtual_file_id, real_file_id)?), + None => None, + }) + } +} + +impl UpmapFromRaFixture for Vec { + fn upmap_from_ra_fixture( + self, + analysis: &RaFixtureAnalysis, + virtual_file_id: FileId, + real_file_id: FileId, + ) -> Result { + upmap_collection(self, analysis, virtual_file_id, real_file_id) + } +} + +impl UpmapFromRaFixture for SmallVec<[T; N]> { + fn upmap_from_ra_fixture( + self, + analysis: &RaFixtureAnalysis, + virtual_file_id: FileId, + real_file_id: FileId, + ) -> Result { + upmap_collection(self, analysis, virtual_file_id, real_file_id) + } +} + +#[allow(clippy::disallowed_types)] +impl + UpmapFromRaFixture for std::collections::HashMap +{ + fn upmap_from_ra_fixture( + self, + analysis: &RaFixtureAnalysis, + virtual_file_id: FileId, + real_file_id: FileId, + ) -> Result { + upmap_collection(self, analysis, virtual_file_id, real_file_id) + } +} + +// A map of `FileId`s is treated as associating the ranges in the values with the keys. +#[allow(clippy::disallowed_types)] +impl UpmapFromRaFixture + for std::collections::HashMap +{ + fn upmap_from_ra_fixture( + self, + analysis: &RaFixtureAnalysis, + _virtual_file_id: FileId, + real_file_id: FileId, + ) -> Result { + if self.is_empty() { + return Ok(self); + } + let result = self + .into_iter() + .filter_map(|(virtual_file_id, value)| { + Some(( + real_file_id, + value.upmap_from_ra_fixture(analysis, virtual_file_id, real_file_id).ok()?, + )) + }) + .collect::>(); + if result.is_empty() { Err(()) } else { Ok(result) } + } +} + +macro_rules! impl_tuple { + () => {}; // Base case. + ( $first:ident, $( $rest:ident, )* ) => { + impl< + $first: UpmapFromRaFixture, + $( $rest: UpmapFromRaFixture, )* + > UpmapFromRaFixture for ( $first, $( $rest, )* ) { + fn upmap_from_ra_fixture( + self, + analysis: &RaFixtureAnalysis, + virtual_file_id: FileId, + real_file_id: FileId, + ) -> Result { + #[allow(non_snake_case)] + let ( $first, $($rest,)* ) = self; + Ok(( + $first.upmap_from_ra_fixture(analysis, virtual_file_id, real_file_id)?, + $( $rest.upmap_from_ra_fixture(analysis, virtual_file_id, real_file_id)?, )* + )) + } + } + + impl_tuple!( $($rest,)* ); + }; +} +impl_tuple!(A, B, C, D, E,); + +impl UpmapFromRaFixture for TextSize { + fn upmap_from_ra_fixture( + self, + analysis: &RaFixtureAnalysis, + virtual_file_id: FileId, + _real_file_id: FileId, + ) -> Result { + analysis.map_offset_up(virtual_file_id, self).ok_or(()) + } +} + +impl UpmapFromRaFixture for TextRange { + fn upmap_from_ra_fixture( + self, + analysis: &RaFixtureAnalysis, + virtual_file_id: FileId, + _real_file_id: FileId, + ) -> Result { + analysis.map_range_up(virtual_file_id, self).next().ok_or(()) + } +} + +// Deliberately do not implement that, as it's easy to get things misbehave and be treated with the wrong FileId: +// +// impl UpmapFromRaFixture for FileId { +// fn upmap_from_ra_fixture( +// self, +// _analysis: &RaFixtureAnalysis, +// _virtual_file_id: FileId, +// real_file_id: FileId, +// ) -> Result { +// Ok(real_file_id) +// } +// } + +impl UpmapFromRaFixture for FilePositionWrapper { + fn upmap_from_ra_fixture( + self, + analysis: &RaFixtureAnalysis, + _virtual_file_id: FileId, + real_file_id: FileId, + ) -> Result { + Ok(FilePositionWrapper { + file_id: real_file_id, + offset: self.offset.upmap_from_ra_fixture(analysis, self.file_id, real_file_id)?, + }) + } +} + +impl UpmapFromRaFixture for FileRangeWrapper { + fn upmap_from_ra_fixture( + self, + analysis: &RaFixtureAnalysis, + _virtual_file_id: FileId, + real_file_id: FileId, + ) -> Result { + Ok(FileRangeWrapper { + file_id: real_file_id, + range: self.range.upmap_from_ra_fixture(analysis, self.file_id, real_file_id)?, + }) + } +} + +#[macro_export] +macro_rules! impl_empty_upmap_from_ra_fixture { + ( $( $ty:ty ),* $(,)? ) => { + $( + impl $crate::ra_fixture::UpmapFromRaFixture for $ty { + fn upmap_from_ra_fixture( + self, + _analysis: &$crate::ra_fixture::RaFixtureAnalysis, + _virtual_file_id: $crate::ra_fixture::FileId, + _real_file_id: $crate::ra_fixture::FileId, + ) -> Result { + Ok(self) + } + } + )* + }; +} + +impl_empty_upmap_from_ra_fixture!( + bool, + i8, + i16, + i32, + i64, + i128, + u8, + u16, + u32, + u64, + u128, + f32, + f64, + &str, + String, + SmolStr, + Documentation, + SymbolKind, + CfgExpr, + ReferenceCategory, +); diff --git a/src/tools/rust-analyzer/crates/ide-db/src/range_mapper.rs b/src/tools/rust-analyzer/crates/ide-db/src/range_mapper.rs new file mode 100644 index 000000000000..ef84888b83b4 --- /dev/null +++ b/src/tools/rust-analyzer/crates/ide-db/src/range_mapper.rs @@ -0,0 +1,65 @@ +//! Maps between ranges in documents. + +use std::cmp::Ordering; + +use stdx::equal_range_by; +use syntax::{TextRange, TextSize}; + +#[derive(Default)] +pub struct RangeMapper { + buf: String, + ranges: Vec<(TextRange, Option)>, +} + +impl RangeMapper { + pub fn add(&mut self, text: &str, source_range: TextRange) { + let len = TextSize::of(text); + assert_eq!(len, source_range.len()); + self.add_impl(text, Some(source_range.start())); + } + + pub fn add_unmapped(&mut self, text: &str) { + self.add_impl(text, None); + } + + fn add_impl(&mut self, text: &str, source: Option) { + let len = TextSize::of(text); + let target_range = TextRange::at(TextSize::of(&self.buf), len); + self.ranges.push((target_range, source.map(|it| TextRange::at(it, len)))); + self.buf.push_str(text); + } + + pub fn take_text(&mut self) -> String { + std::mem::take(&mut self.buf) + } + + pub fn map_range_up(&self, range: TextRange) -> impl Iterator + '_ { + equal_range_by(&self.ranges, |&(r, _)| { + if range.is_empty() && r.contains(range.start()) { + Ordering::Equal + } else { + TextRange::ordering(r, range) + } + }) + .filter_map(move |i| { + let (target_range, source_range) = self.ranges[i]; + let intersection = target_range.intersect(range).unwrap(); + let source_range = source_range?; + Some(intersection - target_range.start() + source_range.start()) + }) + } + + pub fn map_offset_down(&self, offset: TextSize) -> Option { + // Using a binary search here is a bit complicated because of the `None` entries. + // But the number of lines in fixtures is usually low. + let (target_range, source_range) = + self.ranges.iter().find_map(|&(target_range, source_range)| { + let source_range = source_range?; + if !source_range.contains(offset) { + return None; + } + Some((target_range, source_range)) + })?; + Some(offset - source_range.start() + target_range.start()) + } +} diff --git a/src/tools/rust-analyzer/crates/ide-db/src/source_change.rs b/src/tools/rust-analyzer/crates/ide-db/src/source_change.rs index 16c0d8d97a7d..57072bb5ba36 100644 --- a/src/tools/rust-analyzer/crates/ide-db/src/source_change.rs +++ b/src/tools/rust-analyzer/crates/ide-db/src/source_change.rs @@ -10,6 +10,7 @@ use crate::text_edit::{TextEdit, TextEditBuilder}; use crate::{SnippetCap, assists::Command, syntax_helpers::tree_diff::diff}; use base_db::AnchoredPathBuf; use itertools::Itertools; +use macros::UpmapFromRaFixture; use nohash_hasher::IntMap; use rustc_hash::FxHashMap; use span::FileId; @@ -20,7 +21,7 @@ use syntax::{ }; /// An annotation ID associated with an indel, to describe changes. -#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] +#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, UpmapFromRaFixture)] pub struct ChangeAnnotationId(u32); impl fmt::Display for ChangeAnnotationId { diff --git a/src/tools/rust-analyzer/crates/ide-db/src/text_edit.rs b/src/tools/rust-analyzer/crates/ide-db/src/text_edit.rs index 6e9bd7bdcc21..d2a73710d58b 100644 --- a/src/tools/rust-analyzer/crates/ide-db/src/text_edit.rs +++ b/src/tools/rust-analyzer/crates/ide-db/src/text_edit.rs @@ -5,6 +5,7 @@ //! rust-analyzer. use itertools::Itertools; +use macros::UpmapFromRaFixture; pub use span::{TextRange, TextSize}; use std::cmp::max; @@ -13,14 +14,14 @@ use crate::source_change::ChangeAnnotationId; /// `InsertDelete` -- a single "atomic" change to text /// /// Must not overlap with other `InDel`s -#[derive(Debug, Clone, PartialEq, Eq, Hash)] +#[derive(Debug, Clone, PartialEq, Eq, Hash, UpmapFromRaFixture)] pub struct Indel { pub insert: String, /// Refers to offsets in the original text pub delete: TextRange, } -#[derive(Default, Debug, Clone)] +#[derive(Default, Debug, Clone, UpmapFromRaFixture)] pub struct TextEdit { /// Invariant: disjoint and sorted by `delete`. indels: Vec, diff --git a/src/tools/rust-analyzer/crates/ide-diagnostics/src/tests.rs b/src/tools/rust-analyzer/crates/ide-diagnostics/src/tests.rs index 37af05e0d1bb..3dc155efe96b 100644 --- a/src/tools/rust-analyzer/crates/ide-diagnostics/src/tests.rs +++ b/src/tools/rust-analyzer/crates/ide-diagnostics/src/tests.rs @@ -311,7 +311,7 @@ fn minicore_smoke_test() { } fn check(minicore: MiniCore) { - let source = minicore.source_code(); + let source = minicore.source_code(MiniCore::RAW_SOURCE); let mut config = DiagnosticsConfig::test_sample(); // This should be ignored since we conditionally remove code which creates single item use with braces config.disabled.insert("unused_braces".to_owned()); @@ -321,7 +321,7 @@ fn minicore_smoke_test() { } // Checks that there is no diagnostic in minicore for each flag. - for flag in MiniCore::available_flags() { + for flag in MiniCore::available_flags(MiniCore::RAW_SOURCE) { if flag == "clone" { // Clone without copy has `moved-out-of-ref`, so ignoring. // FIXME: Maybe we should merge copy and clone in a single flag? @@ -332,5 +332,5 @@ fn minicore_smoke_test() { } // And one time for all flags, to check codes which are behind multiple flags + prevent name collisions eprintln!("Checking all minicore flags"); - check(MiniCore::from_flags(MiniCore::available_flags())) + check(MiniCore::from_flags(MiniCore::available_flags(MiniCore::RAW_SOURCE))) } diff --git a/src/tools/rust-analyzer/crates/ide/Cargo.toml b/src/tools/rust-analyzer/crates/ide/Cargo.toml index 06d2776ebe87..08ffd391c02d 100644 --- a/src/tools/rust-analyzer/crates/ide/Cargo.toml +++ b/src/tools/rust-analyzer/crates/ide/Cargo.toml @@ -42,6 +42,7 @@ span.workspace = true # ide should depend only on the top-level `hir` package. if you need # something from some `hir-xxx` subpackage, reexport the API via `hir`. hir.workspace = true +macros.workspace = true [target.'cfg(not(any(target_arch = "wasm32", target_os = "emscripten")))'.dependencies] toolchain.workspace = true diff --git a/src/tools/rust-analyzer/crates/ide/src/annotations.rs b/src/tools/rust-analyzer/crates/ide/src/annotations.rs index dec1889926da..36c44044bb5d 100644 --- a/src/tools/rust-analyzer/crates/ide/src/annotations.rs +++ b/src/tools/rust-analyzer/crates/ide/src/annotations.rs @@ -1,6 +1,6 @@ use hir::{HasSource, InFile, InRealFile, Semantics}; use ide_db::{ - FileId, FilePosition, FileRange, FxIndexSet, RootDatabase, defs::Definition, + FileId, FilePosition, FileRange, FxIndexSet, MiniCore, RootDatabase, defs::Definition, helpers::visit_file_defs, }; use itertools::Itertools; @@ -11,7 +11,7 @@ use crate::{ annotations::fn_references::find_all_methods, goto_implementation::goto_implementation, navigation_target, - references::find_all_refs, + references::{FindAllRefsConfig, find_all_refs}, runnables::{Runnable, runnables}, }; @@ -36,7 +36,7 @@ pub enum AnnotationKind { HasReferences { pos: FilePosition, data: Option> }, } -pub struct AnnotationConfig { +pub struct AnnotationConfig<'a> { pub binary_target: bool, pub annotate_runnables: bool, pub annotate_impls: bool, @@ -44,6 +44,7 @@ pub struct AnnotationConfig { pub annotate_method_references: bool, pub annotate_enum_variant_references: bool, pub location: AnnotationLocation, + pub minicore: MiniCore<'a>, } pub enum AnnotationLocation { @@ -53,7 +54,7 @@ pub enum AnnotationLocation { pub(crate) fn annotations( db: &RootDatabase, - config: &AnnotationConfig, + config: &AnnotationConfig<'_>, file_id: FileId, ) -> Vec { let mut annotations = FxIndexSet::default(); @@ -196,13 +197,22 @@ pub(crate) fn annotations( .collect() } -pub(crate) fn resolve_annotation(db: &RootDatabase, mut annotation: Annotation) -> Annotation { +pub(crate) fn resolve_annotation( + db: &RootDatabase, + config: &AnnotationConfig<'_>, + mut annotation: Annotation, +) -> Annotation { match annotation.kind { AnnotationKind::HasImpls { pos, ref mut data } => { *data = goto_implementation(db, pos).map(|range| range.info); } AnnotationKind::HasReferences { pos, ref mut data } => { - *data = find_all_refs(&Semantics::new(db), pos, None).map(|result| { + *data = find_all_refs( + &Semantics::new(db), + pos, + &FindAllRefsConfig { search_scope: None, minicore: config.minicore }, + ) + .map(|result| { result .into_iter() .flat_map(|res| res.references) @@ -228,12 +238,13 @@ fn should_skip_runnable(kind: &RunnableKind, binary_target: bool) -> bool { #[cfg(test)] mod tests { use expect_test::{Expect, expect}; + use ide_db::MiniCore; use crate::{Annotation, AnnotationConfig, fixture}; use super::AnnotationLocation; - const DEFAULT_CONFIG: AnnotationConfig = AnnotationConfig { + const DEFAULT_CONFIG: AnnotationConfig<'_> = AnnotationConfig { binary_target: true, annotate_runnables: true, annotate_impls: true, @@ -241,12 +252,13 @@ mod tests { annotate_method_references: true, annotate_enum_variant_references: true, location: AnnotationLocation::AboveName, + minicore: MiniCore::default(), }; fn check_with_config( #[rust_analyzer::rust_fixture] ra_fixture: &str, expect: Expect, - config: &AnnotationConfig, + config: &AnnotationConfig<'_>, ) { let (analysis, file_id) = fixture::file(ra_fixture); @@ -254,7 +266,7 @@ mod tests { .annotations(config, file_id) .unwrap() .into_iter() - .map(|annotation| analysis.resolve_annotation(annotation).unwrap()) + .map(|annotation| analysis.resolve_annotation(&DEFAULT_CONFIG, annotation).unwrap()) .collect(); expect.assert_debug_eq(&annotations); diff --git a/src/tools/rust-analyzer/crates/ide/src/call_hierarchy.rs b/src/tools/rust-analyzer/crates/ide/src/call_hierarchy.rs index f42cead3501d..aded911a8db1 100644 --- a/src/tools/rust-analyzer/crates/ide/src/call_hierarchy.rs +++ b/src/tools/rust-analyzer/crates/ide/src/call_hierarchy.rs @@ -4,14 +4,16 @@ use std::iter; use hir::Semantics; use ide_db::{ - FileRange, FxIndexMap, RootDatabase, + FileRange, FxIndexMap, MiniCore, RootDatabase, defs::{Definition, NameClass, NameRefClass}, helpers::pick_best_token, search::FileReference, }; use syntax::{AstNode, SyntaxKind::IDENT, ast}; -use crate::{FilePosition, NavigationTarget, RangeInfo, TryToNav, goto_definition}; +use crate::{ + FilePosition, GotoDefinitionConfig, NavigationTarget, RangeInfo, TryToNav, goto_definition, +}; #[derive(Debug, Clone)] pub struct CallItem { @@ -19,22 +21,28 @@ pub struct CallItem { pub ranges: Vec, } -#[derive(Debug, Clone, Copy, PartialEq, Eq)] -pub struct CallHierarchyConfig { +#[derive(Debug, Clone, Copy)] +pub struct CallHierarchyConfig<'a> { /// Whether to exclude tests from the call hierarchy pub exclude_tests: bool, + pub minicore: MiniCore<'a>, } pub(crate) fn call_hierarchy( db: &RootDatabase, position: FilePosition, + config: &CallHierarchyConfig<'_>, ) -> Option>> { - goto_definition::goto_definition(db, position) + goto_definition::goto_definition( + db, + position, + &GotoDefinitionConfig { minicore: config.minicore }, + ) } pub(crate) fn incoming_calls( db: &RootDatabase, - CallHierarchyConfig { exclude_tests }: CallHierarchyConfig, + config: &CallHierarchyConfig<'_>, FilePosition { file_id, offset }: FilePosition, ) -> Option> { let sema = &Semantics::new(db); @@ -71,7 +79,7 @@ pub(crate) fn incoming_calls( }); if let Some((def, nav)) = def_nav { - if exclude_tests && def.is_test(db) { + if config.exclude_tests && def.is_test(db) { continue; } @@ -89,7 +97,7 @@ pub(crate) fn incoming_calls( pub(crate) fn outgoing_calls( db: &RootDatabase, - CallHierarchyConfig { exclude_tests }: CallHierarchyConfig, + config: &CallHierarchyConfig<'_>, FilePosition { file_id, offset }: FilePosition, ) -> Option> { let sema = Semantics::new(db); @@ -119,7 +127,7 @@ pub(crate) fn outgoing_calls( let callable = sema.type_of_expr(&expr)?.original.as_callable(db)?; match callable.kind() { hir::CallableKind::Function(it) => { - if exclude_tests && it.is_test(db) { + if config.exclude_tests && it.is_test(db) { return None; } it.try_to_nav(&sema) @@ -132,7 +140,7 @@ pub(crate) fn outgoing_calls( } ast::CallableExpr::MethodCall(expr) => { let function = sema.resolve_method_call(&expr)?; - if exclude_tests && function.is_test(db) { + if config.exclude_tests && function.is_test(db) { return None; } function @@ -166,7 +174,7 @@ impl CallLocations { #[cfg(test)] mod tests { use expect_test::{Expect, expect}; - use ide_db::FilePosition; + use ide_db::{FilePosition, MiniCore}; use itertools::Itertools; use crate::fixture; @@ -189,21 +197,20 @@ mod tests { ) } + let config = crate::CallHierarchyConfig { exclude_tests, minicore: MiniCore::default() }; let (analysis, pos) = fixture::position(ra_fixture); - let mut navs = analysis.call_hierarchy(pos).unwrap().unwrap().info; + let mut navs = analysis.call_hierarchy(pos, &config).unwrap().unwrap().info; assert_eq!(navs.len(), 1); let nav = navs.pop().unwrap(); expected_nav.assert_eq(&nav.debug_render()); - let config = crate::CallHierarchyConfig { exclude_tests }; - let item_pos = FilePosition { file_id: nav.file_id, offset: nav.focus_or_full_range().start() }; - let incoming_calls = analysis.incoming_calls(config, item_pos).unwrap().unwrap(); + let incoming_calls = analysis.incoming_calls(&config, item_pos).unwrap().unwrap(); expected_incoming.assert_eq(&incoming_calls.into_iter().map(debug_render).join("\n")); - let outgoing_calls = analysis.outgoing_calls(config, item_pos).unwrap().unwrap(); + let outgoing_calls = analysis.outgoing_calls(&config, item_pos).unwrap().unwrap(); expected_outgoing.assert_eq(&outgoing_calls.into_iter().map(debug_render).join("\n")); } diff --git a/src/tools/rust-analyzer/crates/ide/src/goto_declaration.rs b/src/tools/rust-analyzer/crates/ide/src/goto_declaration.rs index 686dbe241293..375ce94bf644 100644 --- a/src/tools/rust-analyzer/crates/ide/src/goto_declaration.rs +++ b/src/tools/rust-analyzer/crates/ide/src/goto_declaration.rs @@ -6,8 +6,8 @@ use ide_db::{ use syntax::{AstNode, SyntaxKind::*, T, ast, match_ast}; use crate::{ - FilePosition, NavigationTarget, RangeInfo, goto_definition::goto_definition, - navigation_target::TryToNav, + FilePosition, GotoDefinitionConfig, NavigationTarget, RangeInfo, + goto_definition::goto_definition, navigation_target::TryToNav, }; // Feature: Go to Declaration @@ -21,6 +21,7 @@ use crate::{ pub(crate) fn goto_declaration( db: &RootDatabase, position @ FilePosition { file_id, offset }: FilePosition, + config: &GotoDefinitionConfig<'_>, ) -> Option>> { let sema = Semantics::new(db); let file = sema.parse_guess_edition(file_id).syntax().clone(); @@ -69,20 +70,27 @@ pub(crate) fn goto_declaration( .flatten() .collect(); - if info.is_empty() { goto_definition(db, position) } else { Some(RangeInfo::new(range, info)) } + if info.is_empty() { + goto_definition(db, position, config) + } else { + Some(RangeInfo::new(range, info)) + } } #[cfg(test)] mod tests { - use ide_db::FileRange; + use ide_db::{FileRange, MiniCore}; use itertools::Itertools; - use crate::fixture; + use crate::{GotoDefinitionConfig, fixture}; + + const TEST_CONFIG: GotoDefinitionConfig<'_> = + GotoDefinitionConfig { minicore: MiniCore::default() }; fn check(#[rust_analyzer::rust_fixture] ra_fixture: &str) { let (analysis, position, expected) = fixture::annotations(ra_fixture); let navs = analysis - .goto_declaration(position) + .goto_declaration(position, &TEST_CONFIG) .unwrap() .expect("no declaration or definition found") .info; diff --git a/src/tools/rust-analyzer/crates/ide/src/goto_definition.rs b/src/tools/rust-analyzer/crates/ide/src/goto_definition.rs index 2dcb13d9e7aa..e335989ab2b0 100644 --- a/src/tools/rust-analyzer/crates/ide/src/goto_definition.rs +++ b/src/tools/rust-analyzer/crates/ide/src/goto_definition.rs @@ -1,5 +1,6 @@ use std::{iter, mem::discriminant}; +use crate::Analysis; use crate::{ FilePosition, NavigationTarget, RangeInfo, TryToNav, UpmappingResult, doc_links::token_as_doc_comment, @@ -8,6 +9,7 @@ use crate::{ use hir::{ AsAssocItem, AssocItem, CallableKind, FileRange, HasCrate, InFile, ModuleDef, Semantics, sym, }; +use ide_db::{MiniCore, ra_fixture::UpmapFromRaFixture}; use ide_db::{ RootDatabase, SymbolKind, base_db::{AnchoredPath, SourceDatabase}, @@ -25,6 +27,11 @@ use syntax::{ match_ast, }; +#[derive(Debug)] +pub struct GotoDefinitionConfig<'a> { + pub minicore: MiniCore<'a>, +} + // Feature: Go to Definition // // Navigates to the definition of an identifier. @@ -39,6 +46,7 @@ use syntax::{ pub(crate) fn goto_definition( db: &RootDatabase, FilePosition { file_id, offset }: FilePosition, + config: &GotoDefinitionConfig<'_>, ) -> Option>> { let sema = &Semantics::new(db); let file = sema.parse_guess_edition(file_id).syntax().clone(); @@ -83,52 +91,64 @@ pub(crate) fn goto_definition( return Some(RangeInfo::new(original_token.text_range(), navs)); } - let navs = sema - .descend_into_macros_no_opaque(original_token.clone(), false) - .into_iter() - .filter_map(|token| { - if let Some(navs) = find_definition_for_known_blanket_dual_impls(sema, &token.value) { - return Some(navs); - } + let tokens = sema.descend_into_macros_no_opaque(original_token.clone(), false); + let mut navs = Vec::new(); + for token in tokens { + if let Some(n) = find_definition_for_known_blanket_dual_impls(sema, &token.value) { + navs.extend(n); + continue; + } - let parent = token.value.parent()?; + if let Some(token) = ast::String::cast(token.value.clone()) + && let Some(original_token) = ast::String::cast(original_token.clone()) + && let Some((analysis, fixture_analysis)) = + Analysis::from_ra_fixture(sema, original_token, &token, config.minicore) + && let Some((virtual_file_id, file_offset)) = fixture_analysis.map_offset_down(offset) + { + return hir::attach_db_allow_change(&analysis.db, || { + goto_definition( + &analysis.db, + FilePosition { file_id: virtual_file_id, offset: file_offset }, + config, + ) + }) + .and_then(|navs| { + navs.upmap_from_ra_fixture(&fixture_analysis, virtual_file_id, file_id).ok() + }); + } - let token_file_id = token.file_id; - if let Some(token) = ast::String::cast(token.value.clone()) - && let Some(x) = - try_lookup_include_path(sema, InFile::new(token_file_id, token), file_id) - { - return Some(vec![x]); - } + let parent = token.value.parent()?; - if ast::TokenTree::can_cast(parent.kind()) - && let Some(x) = try_lookup_macro_def_in_macro_use(sema, token.value) - { - return Some(vec![x]); - } + let token_file_id = token.file_id; + if let Some(token) = ast::String::cast(token.value.clone()) + && let Some(x) = + try_lookup_include_path(sema, InFile::new(token_file_id, token), file_id) + { + navs.push(x); + continue; + } - Some( - IdentClass::classify_node(sema, &parent)? - .definitions() + if ast::TokenTree::can_cast(parent.kind()) + && let Some(x) = try_lookup_macro_def_in_macro_use(sema, token.value) + { + navs.push(x); + continue; + } + + let Some(ident_class) = IdentClass::classify_node(sema, &parent) else { continue }; + navs.extend(ident_class.definitions().into_iter().flat_map(|(def, _)| { + if let Definition::ExternCrateDecl(crate_def) = def { + return crate_def + .resolved_crate(db) + .map(|it| it.root_module().to_nav(sema.db)) .into_iter() - .flat_map(|(def, _)| { - if let Definition::ExternCrateDecl(crate_def) = def { - return crate_def - .resolved_crate(db) - .map(|it| it.root_module().to_nav(sema.db)) - .into_iter() - .flatten() - .collect(); - } - try_filter_trait_item_definition(sema, &def) - .unwrap_or_else(|| def_to_nav(sema, def)) - }) - .collect(), - ) - }) - .flatten() - .unique() - .collect::>(); + .flatten() + .collect(); + } + try_filter_trait_item_definition(sema, &def).unwrap_or_else(|| def_to_nav(sema, def)) + })); + } + let navs = navs.into_iter().unique().collect(); Some(RangeInfo::new(original_token.text_range(), navs)) } @@ -584,15 +604,22 @@ fn expr_to_nav( #[cfg(test)] mod tests { - use crate::fixture; - use ide_db::FileRange; + use crate::{GotoDefinitionConfig, fixture}; + use ide_db::{FileRange, MiniCore}; use itertools::Itertools; use syntax::SmolStr; + const TEST_CONFIG: GotoDefinitionConfig<'_> = + GotoDefinitionConfig { minicore: MiniCore::default() }; + #[track_caller] fn check(#[rust_analyzer::rust_fixture] ra_fixture: &str) { let (analysis, position, expected) = fixture::annotations(ra_fixture); - let navs = analysis.goto_definition(position).unwrap().expect("no definition found").info; + let navs = analysis + .goto_definition(position, &TEST_CONFIG) + .unwrap() + .expect("no definition found") + .info; let cmp = |&FileRange { file_id, range }: &_| (file_id, range.start()); let navs = navs @@ -611,14 +638,22 @@ mod tests { fn check_unresolved(#[rust_analyzer::rust_fixture] ra_fixture: &str) { let (analysis, position) = fixture::position(ra_fixture); - let navs = analysis.goto_definition(position).unwrap().expect("no definition found").info; + let navs = analysis + .goto_definition(position, &TEST_CONFIG) + .unwrap() + .expect("no definition found") + .info; assert!(navs.is_empty(), "didn't expect this to resolve anywhere: {navs:?}") } fn check_name(expected_name: &str, #[rust_analyzer::rust_fixture] ra_fixture: &str) { let (analysis, position, _) = fixture::annotations(ra_fixture); - let navs = analysis.goto_definition(position).unwrap().expect("no definition found").info; + let navs = analysis + .goto_definition(position, &TEST_CONFIG) + .unwrap() + .expect("no definition found") + .info; assert!(navs.len() < 2, "expected single navigation target but encountered {}", navs.len()); let Some(target) = navs.into_iter().next() else { panic!("expected single navigation target but encountered none"); @@ -3961,4 +3996,23 @@ mod prim_str {} "#, ); } + + #[test] + fn ra_fixture() { + check( + r##" +fn fixture(#[rust_analyzer::rust_fixture] ra_fixture: &str) {} + +fn foo() { + fixture(r#" +fn foo() {} +// ^^^ +fn bar() { + f$0oo(); +} + "#) +} + "##, + ); + } } diff --git a/src/tools/rust-analyzer/crates/ide/src/hover.rs b/src/tools/rust-analyzer/crates/ide/src/hover.rs index c4fb6d1a5b4b..e1d18b0c4116 100644 --- a/src/tools/rust-analyzer/crates/ide/src/hover.rs +++ b/src/tools/rust-analyzer/crates/ide/src/hover.rs @@ -11,29 +11,32 @@ use hir::{ db::DefDatabase, }; use ide_db::{ - FileRange, FxIndexSet, Ranker, RootDatabase, + FileRange, FxIndexSet, MiniCore, Ranker, RootDatabase, defs::{Definition, IdentClass, NameRefClass, OperatorClass}, famous_defs::FamousDefs, helpers::pick_best_token, + ra_fixture::UpmapFromRaFixture, }; use itertools::{Itertools, multizip}; -use span::Edition; +use macros::UpmapFromRaFixture; +use span::{Edition, TextRange}; use syntax::{ - AstNode, + AstNode, AstToken, SyntaxKind::{self, *}, SyntaxNode, T, ast, }; use crate::{ - FileId, FilePosition, NavigationTarget, RangeInfo, Runnable, TryToNav, + Analysis, FileId, FilePosition, NavigationTarget, RangeInfo, Runnable, TryToNav, doc_links::token_as_doc_comment, markdown_remove::remove_markdown, markup::Markup, navigation_target::UpmappingResult, runnables::{runnable_fn, runnable_mod}, }; -#[derive(Clone, Debug, PartialEq, Eq)] -pub struct HoverConfig { + +#[derive(Clone, Debug)] +pub struct HoverConfig<'a> { pub links_in_hover: bool, pub memory_layout: Option, pub documentation: bool, @@ -44,6 +47,7 @@ pub struct HoverConfig { pub max_enum_variants_count: Option, pub max_subst_ty_len: SubstTyLen, pub show_drop_glue: bool, + pub minicore: MiniCore<'a>, } #[derive(Clone, Debug, PartialEq, Eq)] @@ -75,7 +79,7 @@ pub enum HoverDocFormat { PlainText, } -#[derive(Debug, Clone, Hash, PartialEq, Eq)] +#[derive(Debug, Clone, Hash, PartialEq, Eq, UpmapFromRaFixture)] pub enum HoverAction { Runnable(Runnable), Implementation(FilePosition), @@ -108,14 +112,14 @@ impl HoverAction { } } -#[derive(Debug, Clone, Eq, PartialEq, Hash)] +#[derive(Debug, Clone, Eq, PartialEq, Hash, UpmapFromRaFixture)] pub struct HoverGotoTypeData { pub mod_path: String, pub nav: NavigationTarget, } /// Contains the results when hovering over an item -#[derive(Clone, Debug, Default, Hash, PartialEq, Eq)] +#[derive(Clone, Debug, Default, Hash, PartialEq, Eq, UpmapFromRaFixture)] pub struct HoverResult { pub markup: Markup, pub actions: Vec, @@ -130,7 +134,7 @@ pub struct HoverResult { pub(crate) fn hover( db: &RootDatabase, frange @ FileRange { file_id, range }: FileRange, - config: &HoverConfig, + config: &HoverConfig<'_>, ) -> Option> { let sema = &hir::Semantics::new(db); let file = sema.parse_guess_edition(file_id).syntax().clone(); @@ -161,7 +165,7 @@ fn hover_offset( sema: &Semantics<'_, RootDatabase>, FilePosition { file_id, offset }: FilePosition, file: SyntaxNode, - config: &HoverConfig, + config: &HoverConfig<'_>, edition: Edition, display_target: DisplayTarget, ) -> Option> { @@ -219,6 +223,21 @@ fn hover_offset( return Some(RangeInfo::new(range, res)); } + if let Some(literal) = ast::String::cast(original_token.clone()) + && let Some((analysis, fixture_analysis)) = + Analysis::from_ra_fixture(sema, literal.clone(), &literal, config.minicore) + { + let (virtual_file_id, virtual_offset) = fixture_analysis.map_offset_down(offset)?; + return analysis + .hover( + config, + FileRange { file_id: virtual_file_id, range: TextRange::empty(virtual_offset) }, + ) + .ok()?? + .upmap_from_ra_fixture(&fixture_analysis, virtual_file_id, file_id) + .ok(); + } + // prefer descending the same token kind in attribute expansions, in normal macros text // equivalency is more important let mut descended = sema.descend_into_macros(original_token.clone()); @@ -383,9 +402,9 @@ fn hover_offset( fn hover_ranged( sema: &Semantics<'_, RootDatabase>, - FileRange { range, .. }: FileRange, + FileRange { file_id, range }: FileRange, file: SyntaxNode, - config: &HoverConfig, + config: &HoverConfig<'_>, edition: Edition, display_target: DisplayTarget, ) -> Option> { @@ -404,6 +423,20 @@ fn hover_ranged( { render::deref_expr(sema, config, prefix_expr, edition, display_target) } + Either::Left(ast::Expr::Literal(literal)) => { + if let Some(literal) = ast::String::cast(literal.token()) + && let Some((analysis, fixture_analysis)) = + Analysis::from_ra_fixture(sema, literal.clone(), &literal, config.minicore) + { + let (virtual_file_id, virtual_range) = fixture_analysis.map_range_down(range)?; + return analysis + .hover(config, FileRange { file_id: virtual_file_id, range: virtual_range }) + .ok()?? + .upmap_from_ra_fixture(&fixture_analysis, virtual_file_id, file_id) + .ok(); + } + None + } _ => None, }; let res = @@ -426,7 +459,7 @@ pub(crate) fn hover_for_definition( scope_node: &SyntaxNode, macro_arm: Option, render_extras: bool, - config: &HoverConfig, + config: &HoverConfig<'_>, edition: Edition, display_target: DisplayTarget, ) -> HoverResult { diff --git a/src/tools/rust-analyzer/crates/ide/src/hover/render.rs b/src/tools/rust-analyzer/crates/ide/src/hover/render.rs index f29ccc985c18..a1eff3aaee78 100644 --- a/src/tools/rust-analyzer/crates/ide/src/hover/render.rs +++ b/src/tools/rust-analyzer/crates/ide/src/hover/render.rs @@ -35,7 +35,7 @@ use crate::{ pub(super) fn type_info_of( sema: &Semantics<'_, RootDatabase>, - _config: &HoverConfig, + _config: &HoverConfig<'_>, expr_or_pat: &Either, edition: Edition, display_target: DisplayTarget, @@ -49,7 +49,7 @@ pub(super) fn type_info_of( pub(super) fn closure_expr( sema: &Semantics<'_, RootDatabase>, - config: &HoverConfig, + config: &HoverConfig<'_>, c: ast::ClosureExpr, edition: Edition, display_target: DisplayTarget, @@ -60,7 +60,7 @@ pub(super) fn closure_expr( pub(super) fn try_expr( sema: &Semantics<'_, RootDatabase>, - _config: &HoverConfig, + _config: &HoverConfig<'_>, try_expr: &ast::TryExpr, edition: Edition, display_target: DisplayTarget, @@ -155,7 +155,7 @@ pub(super) fn try_expr( pub(super) fn deref_expr( sema: &Semantics<'_, RootDatabase>, - _config: &HoverConfig, + _config: &HoverConfig<'_>, deref_expr: &ast::PrefixExpr, edition: Edition, display_target: DisplayTarget, @@ -219,7 +219,7 @@ pub(super) fn deref_expr( pub(super) fn underscore( sema: &Semantics<'_, RootDatabase>, - config: &HoverConfig, + config: &HoverConfig<'_>, token: &SyntaxToken, edition: Edition, display_target: DisplayTarget, @@ -263,7 +263,7 @@ pub(super) fn underscore( pub(super) fn keyword( sema: &Semantics<'_, RootDatabase>, - config: &HoverConfig, + config: &HoverConfig<'_>, token: &SyntaxToken, edition: Edition, display_target: DisplayTarget, @@ -290,7 +290,7 @@ pub(super) fn keyword( /// i.e. `let S {a, ..} = S {a: 1, b: 2}` pub(super) fn struct_rest_pat( sema: &Semantics<'_, RootDatabase>, - _config: &HoverConfig, + _config: &HoverConfig<'_>, pattern: &ast::RecordPat, edition: Edition, display_target: DisplayTarget, @@ -371,7 +371,7 @@ pub(super) fn process_markup( def: Definition, markup: &Markup, markup_range_map: Option, - config: &HoverConfig, + config: &HoverConfig<'_>, ) -> Markup { let markup = markup.as_str(); let markup = if config.links_in_hover { @@ -481,7 +481,7 @@ pub(super) fn definition( macro_arm: Option, render_extras: bool, subst_types: Option<&Vec<(Symbol, Type<'_>)>>, - config: &HoverConfig, + config: &HoverConfig<'_>, edition: Edition, display_target: DisplayTarget, ) -> (Markup, Option) { @@ -979,7 +979,7 @@ fn render_notable_trait( fn type_info( sema: &Semantics<'_, RootDatabase>, - config: &HoverConfig, + config: &HoverConfig<'_>, ty: TypeInfo<'_>, edition: Edition, display_target: DisplayTarget, @@ -1038,7 +1038,7 @@ fn type_info( fn closure_ty( sema: &Semantics<'_, RootDatabase>, - config: &HoverConfig, + config: &HoverConfig<'_>, TypeInfo { original, adjusted }: &TypeInfo<'_>, edition: Edition, display_target: DisplayTarget, diff --git a/src/tools/rust-analyzer/crates/ide/src/hover/tests.rs b/src/tools/rust-analyzer/crates/ide/src/hover/tests.rs index df1800616803..91fb4d0a6715 100644 --- a/src/tools/rust-analyzer/crates/ide/src/hover/tests.rs +++ b/src/tools/rust-analyzer/crates/ide/src/hover/tests.rs @@ -1,5 +1,5 @@ use expect_test::{Expect, expect}; -use ide_db::{FileRange, base_db::SourceDatabase}; +use ide_db::{FileRange, MiniCore, base_db::SourceDatabase}; use syntax::TextRange; use crate::{ @@ -8,7 +8,7 @@ use crate::{ use hir::setup_tracing; -const HOVER_BASE_CONFIG: HoverConfig = HoverConfig { +const HOVER_BASE_CONFIG: HoverConfig<'_> = HoverConfig { links_in_hover: false, memory_layout: Some(MemoryLayoutHoverConfig { size: Some(MemoryLayoutHoverRenderKind::Both), @@ -25,6 +25,7 @@ const HOVER_BASE_CONFIG: HoverConfig = HoverConfig { max_enum_variants_count: Some(5), max_subst_ty_len: super::SubstTyLen::Unlimited, show_drop_glue: true, + minicore: MiniCore::default(), }; fn check_hover_no_result(#[rust_analyzer::rust_fixture] ra_fixture: &str) { diff --git a/src/tools/rust-analyzer/crates/ide/src/inlay_hints.rs b/src/tools/rust-analyzer/crates/ide/src/inlay_hints.rs index f7b09b43813d..21550d5e6665 100644 --- a/src/tools/rust-analyzer/crates/ide/src/inlay_hints.rs +++ b/src/tools/rust-analyzer/crates/ide/src/inlay_hints.rs @@ -8,9 +8,12 @@ use hir::{ ClosureStyle, DisplayTarget, EditionedFileId, HasVisibility, HirDisplay, HirDisplayError, HirWrite, InRealFile, ModuleDef, ModuleDefId, Semantics, sym, }; -use ide_db::{FileRange, RootDatabase, famous_defs::FamousDefs, text_edit::TextEditBuilder}; +use ide_db::{ + FileRange, MiniCore, RootDatabase, famous_defs::FamousDefs, text_edit::TextEditBuilder, +}; use ide_db::{FxHashSet, text_edit::TextEdit}; use itertools::Itertools; +use macros::UpmapFromRaFixture; use smallvec::{SmallVec, smallvec}; use stdx::never; use syntax::{ @@ -37,6 +40,7 @@ mod implicit_static; mod implied_dyn_trait; mod lifetime; mod param_name; +mod ra_fixture; mod range_exclusive; // Feature: Inlay Hints @@ -80,7 +84,7 @@ pub(crate) fn inlay_hints( db: &RootDatabase, file_id: FileId, range_limit: Option, - config: &InlayHintsConfig, + config: &InlayHintsConfig<'_>, ) -> Vec { let _p = tracing::info_span!("inlay_hints").entered(); let sema = Semantics::new(db); @@ -132,7 +136,7 @@ pub(crate) fn inlay_hints_resolve( file_id: FileId, resolve_range: TextRange, hash: u64, - config: &InlayHintsConfig, + config: &InlayHintsConfig<'_>, hasher: impl Fn(&InlayHint) -> u64, ) -> Option { let _p = tracing::info_span!("inlay_hints_resolve").entered(); @@ -208,7 +212,7 @@ fn hints( hints: &mut Vec, ctx: &mut InlayHintCtx, famous_defs @ FamousDefs(sema, _krate): &FamousDefs<'_, '_>, - config: &InlayHintsConfig, + config: &InlayHintsConfig<'_>, file_id: EditionedFileId, display_target: DisplayTarget, node: SyntaxNode, @@ -239,6 +243,7 @@ fn hints( closure_ret::hints(hints, famous_defs, config, display_target, it) }, ast::Expr::RangeExpr(it) => range_exclusive::hints(hints, famous_defs, config, it), + ast::Expr::Literal(it) => ra_fixture::hints(hints, famous_defs.0, file_id, config, it), _ => Some(()), } }, @@ -294,8 +299,8 @@ fn hints( }; } -#[derive(Clone, Debug, PartialEq, Eq)] -pub struct InlayHintsConfig { +#[derive(Clone, Debug)] +pub struct InlayHintsConfig<'a> { pub render_colons: bool, pub type_hints: bool, pub sized_bound: bool, @@ -321,9 +326,10 @@ pub struct InlayHintsConfig { pub max_length: Option, pub closing_brace_hints_min_lines: Option, pub fields_to_resolve: InlayFieldsToResolve, + pub minicore: MiniCore<'a>, } -impl InlayHintsConfig { +impl InlayHintsConfig<'_> { fn lazy_text_edit(&self, finish: impl FnOnce() -> TextEdit) -> LazyProperty { if self.fields_to_resolve.resolve_text_edits { LazyProperty::Lazy @@ -466,7 +472,7 @@ pub enum InlayHintPosition { After, } -#[derive(Debug)] +#[derive(Debug, UpmapFromRaFixture)] pub struct InlayHint { /// The text range this inlay hint applies to. pub range: TextRange, @@ -485,9 +491,10 @@ pub struct InlayHint { } /// A type signaling that a value is either computed, or is available for computation. -#[derive(Clone, Debug)] +#[derive(Clone, Debug, Default, UpmapFromRaFixture)] pub enum LazyProperty { Computed(T), + #[default] Lazy, } @@ -537,7 +544,7 @@ pub enum InlayTooltip { Markdown(String), } -#[derive(Default, Hash)] +#[derive(Default, Hash, UpmapFromRaFixture)] pub struct InlayHintLabel { pub parts: SmallVec<[InlayHintLabelPart; 1]>, } @@ -623,6 +630,7 @@ impl fmt::Debug for InlayHintLabel { } } +#[derive(UpmapFromRaFixture)] pub struct InlayHintLabelPart { pub text: String, /// Source location represented by this label part. The client will use this to fetch the part's @@ -724,7 +732,7 @@ impl InlayHintLabelBuilder<'_> { fn label_of_ty( famous_defs @ FamousDefs(sema, _): &FamousDefs<'_, '_>, - config: &InlayHintsConfig, + config: &InlayHintsConfig<'_>, ty: &hir::Type<'_>, display_target: DisplayTarget, ) -> Option { @@ -734,7 +742,7 @@ fn label_of_ty( mut max_length: Option, ty: &hir::Type<'_>, label_builder: &mut InlayHintLabelBuilder<'_>, - config: &InlayHintsConfig, + config: &InlayHintsConfig<'_>, display_target: DisplayTarget, ) -> Result<(), HirDisplayError> { hir::attach_db(sema.db, || { @@ -829,7 +837,7 @@ fn hint_iterator<'db>( fn ty_to_text_edit( sema: &Semantics<'_, RootDatabase>, - config: &InlayHintsConfig, + config: &InlayHintsConfig<'_>, node_for_hint: &SyntaxNode, ty: &hir::Type<'_>, offset_to_insert_ty: TextSize, @@ -860,6 +868,7 @@ mod tests { use expect_test::Expect; use hir::ClosureStyle; + use ide_db::MiniCore; use itertools::Itertools; use test_utils::extract_annotations; @@ -869,7 +878,7 @@ mod tests { use super::{ClosureReturnTypeHints, GenericParameterHints, InlayFieldsToResolve}; - pub(super) const DISABLED_CONFIG: InlayHintsConfig = InlayHintsConfig { + pub(super) const DISABLED_CONFIG: InlayHintsConfig<'_> = InlayHintsConfig { discriminant_hints: DiscriminantHints::Never, render_colons: false, type_hints: false, @@ -899,8 +908,9 @@ mod tests { fields_to_resolve: InlayFieldsToResolve::empty(), implicit_drop_hints: false, range_exclusive_hints: false, + minicore: MiniCore::default(), }; - pub(super) const TEST_CONFIG: InlayHintsConfig = InlayHintsConfig { + pub(super) const TEST_CONFIG: InlayHintsConfig<'_> = InlayHintsConfig { type_hints: true, parameter_hints: true, chaining_hints: true, @@ -917,7 +927,7 @@ mod tests { #[track_caller] pub(super) fn check_with_config( - config: InlayHintsConfig, + config: InlayHintsConfig<'_>, #[rust_analyzer::rust_fixture] ra_fixture: &str, ) { let (analysis, file_id) = fixture::file(ra_fixture); @@ -936,7 +946,7 @@ mod tests { #[track_caller] pub(super) fn check_expect( - config: InlayHintsConfig, + config: InlayHintsConfig<'_>, #[rust_analyzer::rust_fixture] ra_fixture: &str, expect: Expect, ) { @@ -951,7 +961,7 @@ mod tests { /// expect test. #[track_caller] pub(super) fn check_edit( - config: InlayHintsConfig, + config: InlayHintsConfig<'_>, #[rust_analyzer::rust_fixture] ra_fixture: &str, expect: Expect, ) { @@ -974,7 +984,7 @@ mod tests { #[track_caller] pub(super) fn check_no_edit( - config: InlayHintsConfig, + config: InlayHintsConfig<'_>, #[rust_analyzer::rust_fixture] ra_fixture: &str, ) { let (analysis, file_id) = fixture::file(ra_fixture); diff --git a/src/tools/rust-analyzer/crates/ide/src/inlay_hints/adjustment.rs b/src/tools/rust-analyzer/crates/ide/src/inlay_hints/adjustment.rs index 7231a3194d09..ebb0d5752501 100644 --- a/src/tools/rust-analyzer/crates/ide/src/inlay_hints/adjustment.rs +++ b/src/tools/rust-analyzer/crates/ide/src/inlay_hints/adjustment.rs @@ -23,7 +23,7 @@ use crate::{ pub(super) fn hints( acc: &mut Vec, FamousDefs(sema, _): &FamousDefs<'_, '_>, - config: &InlayHintsConfig, + config: &InlayHintsConfig<'_>, display_target: DisplayTarget, expr: &ast::Expr, ) -> Option<()> { diff --git a/src/tools/rust-analyzer/crates/ide/src/inlay_hints/bind_pat.rs b/src/tools/rust-analyzer/crates/ide/src/inlay_hints/bind_pat.rs index 121b16b97e87..de207c7821da 100644 --- a/src/tools/rust-analyzer/crates/ide/src/inlay_hints/bind_pat.rs +++ b/src/tools/rust-analyzer/crates/ide/src/inlay_hints/bind_pat.rs @@ -20,7 +20,7 @@ use crate::{ pub(super) fn hints( acc: &mut Vec, famous_defs @ FamousDefs(sema, _): &FamousDefs<'_, '_>, - config: &InlayHintsConfig, + config: &InlayHintsConfig<'_>, display_target: DisplayTarget, pat: &ast::IdentPat, ) -> Option<()> { diff --git a/src/tools/rust-analyzer/crates/ide/src/inlay_hints/binding_mode.rs b/src/tools/rust-analyzer/crates/ide/src/inlay_hints/binding_mode.rs index 169ab92342ba..e8d305afb3b9 100644 --- a/src/tools/rust-analyzer/crates/ide/src/inlay_hints/binding_mode.rs +++ b/src/tools/rust-analyzer/crates/ide/src/inlay_hints/binding_mode.rs @@ -15,7 +15,7 @@ use crate::{InlayHint, InlayHintLabel, InlayHintPosition, InlayHintsConfig, Inla pub(super) fn hints( acc: &mut Vec, FamousDefs(sema, _): &FamousDefs<'_, '_>, - config: &InlayHintsConfig, + config: &InlayHintsConfig<'_>, pat: &ast::Pat, ) -> Option<()> { if !config.binding_mode_hints { diff --git a/src/tools/rust-analyzer/crates/ide/src/inlay_hints/bounds.rs b/src/tools/rust-analyzer/crates/ide/src/inlay_hints/bounds.rs index 4abd67b91f5e..c9fbdf3ae754 100644 --- a/src/tools/rust-analyzer/crates/ide/src/inlay_hints/bounds.rs +++ b/src/tools/rust-analyzer/crates/ide/src/inlay_hints/bounds.rs @@ -13,7 +13,7 @@ use crate::{ pub(super) fn hints( acc: &mut Vec, famous_defs @ FamousDefs(sema, _): &FamousDefs<'_, '_>, - config: &InlayHintsConfig, + config: &InlayHintsConfig<'_>, params: ast::GenericParamList, ) -> Option<()> { if !config.sized_bound { diff --git a/src/tools/rust-analyzer/crates/ide/src/inlay_hints/chaining.rs b/src/tools/rust-analyzer/crates/ide/src/inlay_hints/chaining.rs index a8bb652fda22..cf3149c9461b 100644 --- a/src/tools/rust-analyzer/crates/ide/src/inlay_hints/chaining.rs +++ b/src/tools/rust-analyzer/crates/ide/src/inlay_hints/chaining.rs @@ -13,7 +13,7 @@ use super::label_of_ty; pub(super) fn hints( acc: &mut Vec, famous_defs @ FamousDefs(sema, _): &FamousDefs<'_, '_>, - config: &InlayHintsConfig, + config: &InlayHintsConfig<'_>, display_target: DisplayTarget, expr: &ast::Expr, ) -> Option<()> { @@ -93,7 +93,7 @@ mod tests { #[track_caller] pub(super) fn check_expect_clear_loc( - config: InlayHintsConfig, + config: InlayHintsConfig<'_>, #[rust_analyzer::rust_fixture] ra_fixture: &str, expect: Expect, ) { diff --git a/src/tools/rust-analyzer/crates/ide/src/inlay_hints/closing_brace.rs b/src/tools/rust-analyzer/crates/ide/src/inlay_hints/closing_brace.rs index 9d246eda57e0..ab3ce5b05b01 100644 --- a/src/tools/rust-analyzer/crates/ide/src/inlay_hints/closing_brace.rs +++ b/src/tools/rust-analyzer/crates/ide/src/inlay_hints/closing_brace.rs @@ -19,7 +19,7 @@ use crate::{ pub(super) fn hints( acc: &mut Vec, sema: &Semantics<'_, RootDatabase>, - config: &InlayHintsConfig, + config: &InlayHintsConfig<'_>, display_target: DisplayTarget, InRealFile { file_id, value: node }: InRealFile, ) -> Option<()> { diff --git a/src/tools/rust-analyzer/crates/ide/src/inlay_hints/closure_captures.rs b/src/tools/rust-analyzer/crates/ide/src/inlay_hints/closure_captures.rs index 3186a566d2bc..f8d4ddc6eb57 100644 --- a/src/tools/rust-analyzer/crates/ide/src/inlay_hints/closure_captures.rs +++ b/src/tools/rust-analyzer/crates/ide/src/inlay_hints/closure_captures.rs @@ -13,7 +13,7 @@ use crate::{ pub(super) fn hints( acc: &mut Vec, FamousDefs(sema, _): &FamousDefs<'_, '_>, - config: &InlayHintsConfig, + config: &InlayHintsConfig<'_>, closure: ast::ClosureExpr, ) -> Option<()> { if !config.closure_capture_hints { diff --git a/src/tools/rust-analyzer/crates/ide/src/inlay_hints/closure_ret.rs b/src/tools/rust-analyzer/crates/ide/src/inlay_hints/closure_ret.rs index fef1cb83c119..7765dc4f087c 100644 --- a/src/tools/rust-analyzer/crates/ide/src/inlay_hints/closure_ret.rs +++ b/src/tools/rust-analyzer/crates/ide/src/inlay_hints/closure_ret.rs @@ -13,7 +13,7 @@ use crate::{ pub(super) fn hints( acc: &mut Vec, famous_defs @ FamousDefs(sema, _): &FamousDefs<'_, '_>, - config: &InlayHintsConfig, + config: &InlayHintsConfig<'_>, display_target: DisplayTarget, closure: ast::ClosureExpr, ) -> Option<()> { diff --git a/src/tools/rust-analyzer/crates/ide/src/inlay_hints/discriminant.rs b/src/tools/rust-analyzer/crates/ide/src/inlay_hints/discriminant.rs index a2a702835a79..5b9267126f8a 100644 --- a/src/tools/rust-analyzer/crates/ide/src/inlay_hints/discriminant.rs +++ b/src/tools/rust-analyzer/crates/ide/src/inlay_hints/discriminant.rs @@ -17,7 +17,7 @@ use crate::{ pub(super) fn enum_hints( acc: &mut Vec, FamousDefs(sema, _): &FamousDefs<'_, '_>, - config: &InlayHintsConfig, + config: &InlayHintsConfig<'_>, enum_: ast::Enum, ) -> Option<()> { if let DiscriminantHints::Never = config.discriminant_hints { @@ -41,7 +41,7 @@ pub(super) fn enum_hints( fn variant_hints( acc: &mut Vec, - config: &InlayHintsConfig, + config: &InlayHintsConfig<'_>, sema: &Semantics<'_, RootDatabase>, enum_: &ast::Enum, variant: &ast::Variant, diff --git a/src/tools/rust-analyzer/crates/ide/src/inlay_hints/extern_block.rs b/src/tools/rust-analyzer/crates/ide/src/inlay_hints/extern_block.rs index 491018a4dda8..8dd6c4db4c04 100644 --- a/src/tools/rust-analyzer/crates/ide/src/inlay_hints/extern_block.rs +++ b/src/tools/rust-analyzer/crates/ide/src/inlay_hints/extern_block.rs @@ -7,7 +7,7 @@ use crate::{InlayHint, InlayHintsConfig}; pub(super) fn extern_block_hints( acc: &mut Vec, FamousDefs(sema, _): &FamousDefs<'_, '_>, - config: &InlayHintsConfig, + config: &InlayHintsConfig<'_>, extern_block: ast::ExternBlock, ) -> Option<()> { if extern_block.unsafe_token().is_some() { @@ -33,7 +33,7 @@ pub(super) fn extern_block_hints( pub(super) fn fn_hints( acc: &mut Vec, FamousDefs(sema, _): &FamousDefs<'_, '_>, - config: &InlayHintsConfig, + config: &InlayHintsConfig<'_>, fn_: &ast::Fn, extern_block: &ast::ExternBlock, ) -> Option<()> { @@ -51,7 +51,7 @@ pub(super) fn fn_hints( pub(super) fn static_hints( acc: &mut Vec, FamousDefs(sema, _): &FamousDefs<'_, '_>, - config: &InlayHintsConfig, + config: &InlayHintsConfig<'_>, static_: &ast::Static, extern_block: &ast::ExternBlock, ) -> Option<()> { @@ -67,7 +67,7 @@ pub(super) fn static_hints( } fn item_hint( - config: &InlayHintsConfig, + config: &InlayHintsConfig<'_>, extern_block: &ast::ExternBlock, token: SyntaxToken, ) -> InlayHint { diff --git a/src/tools/rust-analyzer/crates/ide/src/inlay_hints/generic_param.rs b/src/tools/rust-analyzer/crates/ide/src/inlay_hints/generic_param.rs index 1fddb6fbe01d..27d14f7a73cd 100644 --- a/src/tools/rust-analyzer/crates/ide/src/inlay_hints/generic_param.rs +++ b/src/tools/rust-analyzer/crates/ide/src/inlay_hints/generic_param.rs @@ -16,7 +16,7 @@ use super::param_name::is_argument_similar_to_param_name; pub(crate) fn hints( acc: &mut Vec, FamousDefs(sema, krate): &FamousDefs<'_, '_>, - config: &InlayHintsConfig, + config: &InlayHintsConfig<'_>, node: AnyHasGenericArgs, ) -> Option<()> { let GenericParameterHints { type_hints, lifetime_hints, const_hints } = diff --git a/src/tools/rust-analyzer/crates/ide/src/inlay_hints/implicit_drop.rs b/src/tools/rust-analyzer/crates/ide/src/inlay_hints/implicit_drop.rs index 1e272fe3ba82..951a672d4b79 100644 --- a/src/tools/rust-analyzer/crates/ide/src/inlay_hints/implicit_drop.rs +++ b/src/tools/rust-analyzer/crates/ide/src/inlay_hints/implicit_drop.rs @@ -23,7 +23,7 @@ use crate::{InlayHint, InlayHintLabel, InlayHintPosition, InlayHintsConfig, Inla pub(super) fn hints( acc: &mut Vec, FamousDefs(sema, _): &FamousDefs<'_, '_>, - config: &InlayHintsConfig, + config: &InlayHintsConfig<'_>, display_target: hir::DisplayTarget, node: &ast::Fn, ) -> Option<()> { @@ -147,7 +147,7 @@ mod tests { inlay_hints::tests::{DISABLED_CONFIG, check_with_config}, }; - const ONLY_DROP_CONFIG: InlayHintsConfig = + const ONLY_DROP_CONFIG: InlayHintsConfig<'_> = InlayHintsConfig { implicit_drop_hints: true, ..DISABLED_CONFIG }; #[test] diff --git a/src/tools/rust-analyzer/crates/ide/src/inlay_hints/implicit_static.rs b/src/tools/rust-analyzer/crates/ide/src/inlay_hints/implicit_static.rs index bddce904dfde..0492991790c8 100644 --- a/src/tools/rust-analyzer/crates/ide/src/inlay_hints/implicit_static.rs +++ b/src/tools/rust-analyzer/crates/ide/src/inlay_hints/implicit_static.rs @@ -15,7 +15,7 @@ use crate::{InlayHint, InlayHintPosition, InlayHintsConfig, InlayKind, LifetimeE pub(super) fn hints( acc: &mut Vec, FamousDefs(_sema, _): &FamousDefs<'_, '_>, - config: &InlayHintsConfig, + config: &InlayHintsConfig<'_>, statik_or_const: Either, ) -> Option<()> { if config.lifetime_elision_hints != LifetimeElisionHints::Always { diff --git a/src/tools/rust-analyzer/crates/ide/src/inlay_hints/implied_dyn_trait.rs b/src/tools/rust-analyzer/crates/ide/src/inlay_hints/implied_dyn_trait.rs index 0da1785234ae..562eb1e00213 100644 --- a/src/tools/rust-analyzer/crates/ide/src/inlay_hints/implied_dyn_trait.rs +++ b/src/tools/rust-analyzer/crates/ide/src/inlay_hints/implied_dyn_trait.rs @@ -11,7 +11,7 @@ use crate::{InlayHint, InlayHintLabel, InlayHintPosition, InlayHintsConfig, Inla pub(super) fn hints( acc: &mut Vec, FamousDefs(sema, _): &FamousDefs<'_, '_>, - config: &InlayHintsConfig, + config: &InlayHintsConfig<'_>, path: Either, ) -> Option<()> { let parent = path.syntax().parent()?; diff --git a/src/tools/rust-analyzer/crates/ide/src/inlay_hints/lifetime.rs b/src/tools/rust-analyzer/crates/ide/src/inlay_hints/lifetime.rs index a89c53e00b3b..4982b60f1dc8 100644 --- a/src/tools/rust-analyzer/crates/ide/src/inlay_hints/lifetime.rs +++ b/src/tools/rust-analyzer/crates/ide/src/inlay_hints/lifetime.rs @@ -21,7 +21,7 @@ pub(super) fn fn_hints( acc: &mut Vec, ctx: &mut InlayHintCtx, fd: &FamousDefs<'_, '_>, - config: &InlayHintsConfig, + config: &InlayHintsConfig<'_>, func: ast::Fn, ) -> Option<()> { if config.lifetime_elision_hints == LifetimeElisionHints::Never { @@ -70,7 +70,7 @@ pub(super) fn fn_ptr_hints( acc: &mut Vec, ctx: &mut InlayHintCtx, fd: &FamousDefs<'_, '_>, - config: &InlayHintsConfig, + config: &InlayHintsConfig<'_>, func: ast::FnPtrType, ) -> Option<()> { if config.lifetime_elision_hints == LifetimeElisionHints::Never { @@ -135,7 +135,7 @@ pub(super) fn fn_path_hints( acc: &mut Vec, ctx: &mut InlayHintCtx, fd: &FamousDefs<'_, '_>, - config: &InlayHintsConfig, + config: &InlayHintsConfig<'_>, func: &ast::PathType, ) -> Option<()> { if config.lifetime_elision_hints == LifetimeElisionHints::Never { @@ -196,7 +196,7 @@ fn hints_( acc: &mut Vec, ctx: &mut InlayHintCtx, FamousDefs(_, _): &FamousDefs<'_, '_>, - config: &InlayHintsConfig, + config: &InlayHintsConfig<'_>, params: impl Iterator, ast::Type)>, generic_param_list: Option, ret_type: Option, diff --git a/src/tools/rust-analyzer/crates/ide/src/inlay_hints/param_name.rs b/src/tools/rust-analyzer/crates/ide/src/inlay_hints/param_name.rs index 754707784055..3e555e88303d 100644 --- a/src/tools/rust-analyzer/crates/ide/src/inlay_hints/param_name.rs +++ b/src/tools/rust-analyzer/crates/ide/src/inlay_hints/param_name.rs @@ -18,7 +18,7 @@ use crate::{InlayHint, InlayHintLabel, InlayHintPosition, InlayHintsConfig, Inla pub(super) fn hints( acc: &mut Vec, FamousDefs(sema, krate): &FamousDefs<'_, '_>, - config: &InlayHintsConfig, + config: &InlayHintsConfig<'_>, file_id: EditionedFileId, expr: ast::Expr, ) -> Option<()> { diff --git a/src/tools/rust-analyzer/crates/ide/src/inlay_hints/ra_fixture.rs b/src/tools/rust-analyzer/crates/ide/src/inlay_hints/ra_fixture.rs new file mode 100644 index 000000000000..bee18416424c --- /dev/null +++ b/src/tools/rust-analyzer/crates/ide/src/inlay_hints/ra_fixture.rs @@ -0,0 +1,32 @@ +//! Injected inlay hints for `#[rust_analyzer::rust_fixture]`. + +use hir::{EditionedFileId, Semantics}; +use ide_db::{RootDatabase, impl_empty_upmap_from_ra_fixture, ra_fixture::UpmapFromRaFixture}; +use syntax::{AstToken, ast}; + +use crate::{Analysis, InlayHint, InlayHintPosition, InlayHintsConfig, InlayKind, InlayTooltip}; + +pub(super) fn hints( + acc: &mut Vec, + sema: &Semantics<'_, RootDatabase>, + file_id: EditionedFileId, + config: &InlayHintsConfig<'_>, + literal: ast::Literal, +) -> Option<()> { + let file_id = file_id.file_id(sema.db); + let literal = ast::String::cast(literal.token())?; + let (analysis, fixture_analysis) = + Analysis::from_ra_fixture(sema, literal.clone(), &literal, config.minicore)?; + for virtual_file_id in fixture_analysis.files() { + acc.extend( + analysis + .inlay_hints(config, virtual_file_id, None) + .ok()? + .upmap_from_ra_fixture(&fixture_analysis, virtual_file_id, file_id) + .ok()?, + ); + } + Some(()) +} + +impl_empty_upmap_from_ra_fixture!(InlayHintPosition, InlayKind, InlayTooltip); diff --git a/src/tools/rust-analyzer/crates/ide/src/inlay_hints/range_exclusive.rs b/src/tools/rust-analyzer/crates/ide/src/inlay_hints/range_exclusive.rs index 47bd6d737f82..a446908e736b 100644 --- a/src/tools/rust-analyzer/crates/ide/src/inlay_hints/range_exclusive.rs +++ b/src/tools/rust-analyzer/crates/ide/src/inlay_hints/range_exclusive.rs @@ -11,7 +11,7 @@ use crate::{InlayHint, InlayHintsConfig}; pub(super) fn hints( acc: &mut Vec, FamousDefs(_sema, _): &FamousDefs<'_, '_>, - config: &InlayHintsConfig, + config: &InlayHintsConfig<'_>, range: impl ast::RangeItem, ) -> Option<()> { (config.range_exclusive_hints && range.end().is_some()) diff --git a/src/tools/rust-analyzer/crates/ide/src/lib.rs b/src/tools/rust-analyzer/crates/ide/src/lib.rs index f7d21c947950..857252832ffe 100644 --- a/src/tools/rust-analyzer/crates/ide/src/lib.rs +++ b/src/tools/rust-analyzer/crates/ide/src/lib.rs @@ -62,7 +62,7 @@ use std::panic::{AssertUnwindSafe, UnwindSafe}; use cfg::CfgOptions; use fetch_crates::CrateInfo; -use hir::{ChangeWithProcMacros, EditionedFileId, crate_def_map, db::HirDatabase, sym}; +use hir::{ChangeWithProcMacros, EditionedFileId, crate_def_map, sym}; use ide_db::{ FxHashMap, FxIndexSet, LineIndexDatabase, base_db::{ @@ -71,7 +71,9 @@ use ide_db::{ }, prime_caches, symbol_index, }; -use syntax::SourceFile; +use ide_db::{MiniCore, ra_fixture::RaFixtureAnalysis}; +use macros::UpmapFromRaFixture; +use syntax::{SourceFile, ast}; use triomphe::Arc; use view_memory_layout::{RecursiveMemoryLayout, view_memory_layout}; @@ -83,6 +85,7 @@ pub use crate::{ expand_macro::ExpandedMacro, file_structure::{FileStructureConfig, StructureNode, StructureNodeKind}, folding_ranges::{Fold, FoldKind}, + goto_definition::GotoDefinitionConfig, highlight_related::{HighlightRelatedConfig, HighlightedRange}, hover::{ HoverAction, HoverConfig, HoverDocFormat, HoverGotoTypeData, HoverResult, @@ -102,7 +105,7 @@ pub use crate::{ }, move_item::Direction, navigation_target::{NavigationTarget, TryToNav, UpmappingResult}, - references::ReferenceSearchResult, + references::{FindAllRefsConfig, ReferenceSearchResult}, rename::RenameError, runnables::{Runnable, RunnableKind, TestId, UpdateTest}, signature_help::SignatureHelp, @@ -144,7 +147,7 @@ pub use syntax::{TextRange, TextSize}; pub type Cancellable = Result; /// Info associated with a text range. -#[derive(Debug)] +#[derive(Debug, UpmapFromRaFixture)] pub struct RangeInfo { pub range: TextRange, pub info: T, @@ -274,6 +277,28 @@ impl Analysis { (host.analysis(), file_id) } + pub(crate) fn from_ra_fixture( + sema: &Semantics<'_, RootDatabase>, + literal: ast::String, + expanded: &ast::String, + minicore: MiniCore<'_>, + ) -> Option<(Analysis, RaFixtureAnalysis)> { + Self::from_ra_fixture_with_on_cursor(sema, literal, expanded, minicore, &mut |_| {}) + } + + /// Like [`Analysis::from_ra_fixture()`], but also calls `on_cursor` with the cursor position. + pub(crate) fn from_ra_fixture_with_on_cursor( + sema: &Semantics<'_, RootDatabase>, + literal: ast::String, + expanded: &ast::String, + minicore: MiniCore<'_>, + on_cursor: &mut dyn FnMut(TextRange), + ) -> Option<(Analysis, RaFixtureAnalysis)> { + let analysis = + RaFixtureAnalysis::analyze_ra_fixture(sema, literal, expanded, minicore, on_cursor)?; + Some((Analysis { db: analysis.db.clone() }, analysis)) + } + /// Debug info about the current state of the analysis. pub fn status(&self, file_id: Option) -> Cancellable { self.with_db(|db| status::status(db, file_id)) @@ -446,7 +471,7 @@ impl Analysis { /// Returns a list of the places in the file where type hints can be displayed. pub fn inlay_hints( &self, - config: &InlayHintsConfig, + config: &InlayHintsConfig<'_>, file_id: FileId, range: Option, ) -> Cancellable> { @@ -454,7 +479,7 @@ impl Analysis { } pub fn inlay_hints_resolve( &self, - config: &InlayHintsConfig, + config: &InlayHintsConfig<'_>, file_id: FileId, resolve_range: TextRange, hash: u64, @@ -495,16 +520,18 @@ impl Analysis { pub fn goto_definition( &self, position: FilePosition, + config: &GotoDefinitionConfig<'_>, ) -> Cancellable>>> { - self.with_db(|db| goto_definition::goto_definition(db, position)) + self.with_db(|db| goto_definition::goto_definition(db, position, config)) } /// Returns the declaration from the symbol at `position`. pub fn goto_declaration( &self, position: FilePosition, + config: &GotoDefinitionConfig<'_>, ) -> Cancellable>>> { - self.with_db(|db| goto_declaration::goto_declaration(db, position)) + self.with_db(|db| goto_declaration::goto_declaration(db, position, config)) } /// Returns the impls from the symbol at `position`. @@ -526,19 +553,16 @@ impl Analysis { pub fn find_all_refs( &self, position: FilePosition, - search_scope: Option, + config: &FindAllRefsConfig<'_>, ) -> Cancellable>> { - let search_scope = AssertUnwindSafe(search_scope); - self.with_db(|db| { - let _ = &search_scope; - references::find_all_refs(&Semantics::new(db), position, search_scope.0) - }) + let config = AssertUnwindSafe(config); + self.with_db(|db| references::find_all_refs(&Semantics::new(db), position, &config)) } /// Returns a short text describing element at position. pub fn hover( &self, - config: &HoverConfig, + config: &HoverConfig<'_>, range: FileRange, ) -> Cancellable>> { self.with_db(|db| hover::hover(db, range, config)) @@ -576,14 +600,15 @@ impl Analysis { pub fn call_hierarchy( &self, position: FilePosition, + config: &CallHierarchyConfig<'_>, ) -> Cancellable>>> { - self.with_db(|db| call_hierarchy::call_hierarchy(db, position)) + self.with_db(|db| call_hierarchy::call_hierarchy(db, position, config)) } /// Computes incoming calls for the given file position. pub fn incoming_calls( &self, - config: CallHierarchyConfig, + config: &CallHierarchyConfig<'_>, position: FilePosition, ) -> Cancellable>> { self.with_db(|db| call_hierarchy::incoming_calls(db, config, position)) @@ -592,7 +617,7 @@ impl Analysis { /// Computes outgoing calls for the given file position. pub fn outgoing_calls( &self, - config: CallHierarchyConfig, + config: &CallHierarchyConfig<'_>, position: FilePosition, ) -> Cancellable>> { self.with_db(|db| call_hierarchy::outgoing_calls(db, config, position)) @@ -675,28 +700,22 @@ impl Analysis { /// Computes syntax highlighting for the given file pub fn highlight( &self, - highlight_config: HighlightConfig, + highlight_config: HighlightConfig<'_>, file_id: FileId, ) -> Cancellable> { - // highlighting may construct a new database for "speculative" execution, so we can't currently attach the database - // highlighting instead sets up the attach hook where neceesary for the trait solver - Cancelled::catch(|| { - syntax_highlighting::highlight(&self.db, highlight_config, file_id, None) - }) + self.with_db(|db| syntax_highlighting::highlight(db, &highlight_config, file_id, None)) } /// Computes syntax highlighting for the given file range. pub fn highlight_range( &self, - highlight_config: HighlightConfig, + highlight_config: HighlightConfig<'_>, frange: FileRange, ) -> Cancellable> { - // highlighting may construct a new database for "speculative" execution, so we can't currently attach the database - // highlighting instead sets up the attach hook where neceesary for the trait solver - Cancelled::catch(|| { + self.with_db(|db| { syntax_highlighting::highlight( - &self.db, - highlight_config, + db, + &highlight_config, frange.file_id, Some(frange.range), ) @@ -706,22 +725,18 @@ impl Analysis { /// Computes syntax highlighting for the given file. pub fn highlight_as_html_with_config( &self, - config: HighlightConfig, + config: HighlightConfig<'_>, file_id: FileId, rainbow: bool, ) -> Cancellable { - // highlighting may construct a new database for "speculative" execution, so we can't currently attach the database - // highlighting instead sets up the attach hook where neceesary for the trait solver - Cancelled::catch(|| { - syntax_highlighting::highlight_as_html_with_config(&self.db, config, file_id, rainbow) + self.with_db(|db| { + syntax_highlighting::highlight_as_html_with_config(db, &config, file_id, rainbow) }) } /// Computes syntax highlighting for the given file. pub fn highlight_as_html(&self, file_id: FileId, rainbow: bool) -> Cancellable { - // highlighting may construct a new database for "speculative" execution, so we can't currently attach the database - // highlighting instead sets up the attach hook where neceesary for the trait solver - Cancelled::catch(|| syntax_highlighting::highlight_as_html(&self.db, file_id, rainbow)) + self.with_db(|db| syntax_highlighting::highlight_as_html(db, file_id, rainbow)) } /// Computes completions at the given position. @@ -853,14 +868,18 @@ impl Analysis { pub fn annotations( &self, - config: &AnnotationConfig, + config: &AnnotationConfig<'_>, file_id: FileId, ) -> Cancellable> { self.with_db(|db| annotations::annotations(db, config, file_id)) } - pub fn resolve_annotation(&self, annotation: Annotation) -> Cancellable { - self.with_db(|db| annotations::resolve_annotation(db, annotation)) + pub fn resolve_annotation( + &self, + config: &AnnotationConfig<'_>, + annotation: Annotation, + ) -> Cancellable { + self.with_db(|db| annotations::resolve_annotation(db, config, annotation)) } pub fn move_item( @@ -899,12 +918,8 @@ impl Analysis { where F: FnOnce(&RootDatabase) -> T + std::panic::UnwindSafe, { - hir::attach_db(&self.db, || { - // the trait solver code may invoke `as_view` outside of queries, - // so technically we might run into a panic in salsa if the downcaster has not yet been registered. - HirDatabase::zalsa_register_downcaster(&self.db); - Cancelled::catch(|| f(&self.db)) - }) + // We use `attach_db_allow_change()` and not `attach_db()` because fixture injection can change the database. + hir::attach_db_allow_change(&self.db, || Cancelled::catch(|| f(&self.db))) } } diff --git a/src/tools/rust-analyzer/crates/ide/src/markup.rs b/src/tools/rust-analyzer/crates/ide/src/markup.rs index 750d12542605..3eb9986c120f 100644 --- a/src/tools/rust-analyzer/crates/ide/src/markup.rs +++ b/src/tools/rust-analyzer/crates/ide/src/markup.rs @@ -5,6 +5,8 @@ //! what is used by LSP, so let's keep it simple. use std::fmt; +use ide_db::impl_empty_upmap_from_ra_fixture; + #[derive(Clone, Default, Debug, Hash, PartialEq, Eq)] pub struct Markup { text: String, @@ -39,3 +41,5 @@ impl Markup { format!("```text\n{contents}\n```").into() } } + +impl_empty_upmap_from_ra_fixture!(Markup); diff --git a/src/tools/rust-analyzer/crates/ide/src/navigation_target.rs b/src/tools/rust-analyzer/crates/ide/src/navigation_target.rs index db1298385b11..40580080c089 100644 --- a/src/tools/rust-analyzer/crates/ide/src/navigation_target.rs +++ b/src/tools/rust-analyzer/crates/ide/src/navigation_target.rs @@ -14,6 +14,7 @@ use ide_db::{ defs::{Definition, find_std_module}, documentation::{Documentation, HasDocs}, famous_defs::FamousDefs, + ra_fixture::UpmapFromRaFixture, }; use span::Edition; use stdx::never; @@ -78,6 +79,44 @@ impl fmt::Debug for NavigationTarget { } } +impl UpmapFromRaFixture for NavigationTarget { + fn upmap_from_ra_fixture( + self, + analysis: &ide_db::ra_fixture::RaFixtureAnalysis, + _virtual_file_id: FileId, + real_file_id: FileId, + ) -> Result { + let virtual_file_id = self.file_id; + Ok(NavigationTarget { + file_id: real_file_id, + full_range: self.full_range.upmap_from_ra_fixture( + analysis, + virtual_file_id, + real_file_id, + )?, + focus_range: self.focus_range.upmap_from_ra_fixture( + analysis, + virtual_file_id, + real_file_id, + )?, + name: self.name.upmap_from_ra_fixture(analysis, virtual_file_id, real_file_id)?, + kind: self.kind.upmap_from_ra_fixture(analysis, virtual_file_id, real_file_id)?, + container_name: self.container_name.upmap_from_ra_fixture( + analysis, + virtual_file_id, + real_file_id, + )?, + description: self.description.upmap_from_ra_fixture( + analysis, + virtual_file_id, + real_file_id, + )?, + docs: self.docs.upmap_from_ra_fixture(analysis, virtual_file_id, real_file_id)?, + alias: self.alias.upmap_from_ra_fixture(analysis, virtual_file_id, real_file_id)?, + }) + } +} + pub(crate) trait ToNav { fn to_nav(&self, db: &RootDatabase) -> UpmappingResult; } diff --git a/src/tools/rust-analyzer/crates/ide/src/references.rs b/src/tools/rust-analyzer/crates/ide/src/references.rs index 0189939eac31..a53a19299727 100644 --- a/src/tools/rust-analyzer/crates/ide/src/references.rs +++ b/src/tools/rust-analyzer/crates/ide/src/references.rs @@ -19,14 +19,17 @@ use hir::{PathResolution, Semantics}; use ide_db::{ - FileId, RootDatabase, + FileId, MiniCore, RootDatabase, defs::{Definition, NameClass, NameRefClass}, helpers::pick_best_token, + ra_fixture::UpmapFromRaFixture, search::{ReferenceCategory, SearchScope, UsageSearchResult}, }; use itertools::Itertools; +use macros::UpmapFromRaFixture; use nohash_hasher::IntMap; use span::Edition; +use syntax::AstToken; use syntax::{ AstNode, SyntaxKind::*, @@ -35,10 +38,12 @@ use syntax::{ match_ast, }; -use crate::{FilePosition, HighlightedRange, NavigationTarget, TryToNav, highlight_related}; +use crate::{ + Analysis, FilePosition, HighlightedRange, NavigationTarget, TryToNav, highlight_related, +}; /// Result of a reference search operation. -#[derive(Debug, Clone)] +#[derive(Debug, Clone, UpmapFromRaFixture)] pub struct ReferenceSearchResult { /// Information about the declaration site of the searched item. /// For ADTs (structs/enums), this points to the type definition. @@ -54,7 +59,7 @@ pub struct ReferenceSearchResult { } /// Information about the declaration site of a searched item. -#[derive(Debug, Clone)] +#[derive(Debug, Clone, UpmapFromRaFixture)] pub struct Declaration { /// Navigation information to jump to the declaration pub nav: NavigationTarget, @@ -82,6 +87,12 @@ pub struct Declaration { // // ![Find All References](https://user-images.githubusercontent.com/48062697/113020670-b7c34f00-917a-11eb-8003-370ac5f2b3cb.gif) +#[derive(Debug)] +pub struct FindAllRefsConfig<'a> { + pub search_scope: Option, + pub minicore: MiniCore<'a>, +} + /// Find all references to the item at the given position. /// /// # Arguments @@ -110,14 +121,14 @@ pub struct Declaration { pub(crate) fn find_all_refs( sema: &Semantics<'_, RootDatabase>, position: FilePosition, - search_scope: Option, + config: &FindAllRefsConfig<'_>, ) -> Option> { let _p = tracing::info_span!("find_all_refs").entered(); let syntax = sema.parse_guess_edition(position.file_id).syntax().clone(); let make_searcher = |literal_search: bool| { move |def: Definition| { let mut usages = - def.usages(sema).set_scope(search_scope.as_ref()).include_self_refs().all(); + def.usages(sema).set_scope(config.search_scope.as_ref()).include_self_refs().all(); if literal_search { retain_adt_literal_usages(&mut usages, def, sema); } @@ -165,6 +176,20 @@ pub(crate) fn find_all_refs( return Some(vec![res]); } + if let Some(token) = syntax.token_at_offset(position.offset).left_biased() + && let Some(token) = ast::String::cast(token.clone()) + && let Some((analysis, fixture_analysis)) = + Analysis::from_ra_fixture(sema, token.clone(), &token, config.minicore) + && let Some((virtual_file_id, file_offset)) = + fixture_analysis.map_offset_down(position.offset) + { + return analysis + .find_all_refs(FilePosition { file_id: virtual_file_id, offset: file_offset }, config) + .ok()?? + .upmap_from_ra_fixture(&fixture_analysis, virtual_file_id, position.file_id) + .ok(); + } + match name_for_constructor_search(&syntax, position) { Some(name) => { let def = match NameClass::classify(sema, &name)? { @@ -433,10 +458,10 @@ fn handle_control_flow_keywords( mod tests { use expect_test::{Expect, expect}; use hir::EditionedFileId; - use ide_db::{FileId, RootDatabase}; + use ide_db::{FileId, MiniCore, RootDatabase}; use stdx::format_to; - use crate::{SearchScope, fixture}; + use crate::{SearchScope, fixture, references::FindAllRefsConfig}; #[test] fn exclude_tests() { @@ -1513,8 +1538,11 @@ fn main() { expect: Expect, ) { let (analysis, pos) = fixture::position(ra_fixture); - let refs = - analysis.find_all_refs(pos, search_scope.map(|it| it(&analysis.db))).unwrap().unwrap(); + let config = FindAllRefsConfig { + search_scope: search_scope.map(|it| it(&analysis.db)), + minicore: MiniCore::default(), + }; + let refs = analysis.find_all_refs(pos, &config).unwrap().unwrap(); let mut actual = String::new(); for mut refs in refs { diff --git a/src/tools/rust-analyzer/crates/ide/src/runnables.rs b/src/tools/rust-analyzer/crates/ide/src/runnables.rs index cc1bbfbe20d6..494701d97def 100644 --- a/src/tools/rust-analyzer/crates/ide/src/runnables.rs +++ b/src/tools/rust-analyzer/crates/ide/src/runnables.rs @@ -8,6 +8,7 @@ use hir::{ sym, }; use ide_assists::utils::{has_test_related_attribute, test_related_attribute_syn}; +use ide_db::impl_empty_upmap_from_ra_fixture; use ide_db::{ FilePosition, FxHashMap, FxIndexMap, FxIndexSet, RootDatabase, SymbolKind, base_db::RootQueryDb, @@ -17,6 +18,7 @@ use ide_db::{ search::{FileReferenceNode, SearchScope}, }; use itertools::Itertools; +use macros::UpmapFromRaFixture; use smallvec::SmallVec; use span::{Edition, TextSize}; use stdx::format_to; @@ -28,7 +30,7 @@ use syntax::{ use crate::{FileId, NavigationTarget, ToNav, TryToNav, references}; -#[derive(Debug, Clone, Hash, PartialEq, Eq)] +#[derive(Debug, Clone, Hash, PartialEq, Eq, UpmapFromRaFixture)] pub struct Runnable { pub use_name_in_title: bool, pub nav: NavigationTarget, @@ -37,6 +39,8 @@ pub struct Runnable { pub update_test: UpdateTest, } +impl_empty_upmap_from_ra_fixture!(RunnableKind, UpdateTest); + #[derive(Debug, Clone, Hash, PartialEq, Eq)] pub enum TestId { Name(SmolStr), diff --git a/src/tools/rust-analyzer/crates/ide/src/static_index.rs b/src/tools/rust-analyzer/crates/ide/src/static_index.rs index 453d6f537a8b..e261928c413f 100644 --- a/src/tools/rust-analyzer/crates/ide/src/static_index.rs +++ b/src/tools/rust-analyzer/crates/ide/src/static_index.rs @@ -4,7 +4,7 @@ use arrayvec::ArrayVec; use hir::{Crate, Module, Semantics, db::HirDatabase}; use ide_db::{ - FileId, FileRange, FxHashMap, FxHashSet, RootDatabase, + FileId, FileRange, FxHashMap, FxHashSet, MiniCore, RootDatabase, base_db::{RootQueryDb, SourceDatabase, VfsPath}, defs::{Definition, IdentClass}, documentation::Documentation, @@ -184,6 +184,7 @@ impl StaticIndex<'_> { closing_brace_hints_min_lines: Some(25), fields_to_resolve: InlayFieldsToResolve::empty(), range_exclusive_hints: false, + minicore: MiniCore::default(), }, file_id, None, @@ -215,6 +216,7 @@ impl StaticIndex<'_> { max_enum_variants_count: Some(5), max_subst_ty_len: SubstTyLen::Unlimited, show_drop_glue: true, + minicore: MiniCore::default(), }; let tokens = tokens.filter(|token| { matches!( diff --git a/src/tools/rust-analyzer/crates/ide/src/syntax_highlighting.rs b/src/tools/rust-analyzer/crates/ide/src/syntax_highlighting.rs index 0da9ee097ac3..66895cb0b053 100644 --- a/src/tools/rust-analyzer/crates/ide/src/syntax_highlighting.rs +++ b/src/tools/rust-analyzer/crates/ide/src/syntax_highlighting.rs @@ -1,7 +1,6 @@ pub(crate) mod tags; mod highlights; -mod injector; mod escape; mod format; @@ -16,7 +15,7 @@ use std::ops::ControlFlow; use either::Either; use hir::{DefWithBody, EditionedFileId, InFile, InRealFile, MacroKind, Name, Semantics}; -use ide_db::{FxHashMap, FxHashSet, Ranker, RootDatabase, SymbolKind}; +use ide_db::{FxHashMap, FxHashSet, MiniCore, Ranker, RootDatabase, SymbolKind}; use syntax::{ AstNode, AstToken, NodeOrToken, SyntaxKind::*, @@ -44,8 +43,8 @@ pub struct HlRange { pub binding_hash: Option, } -#[derive(Copy, Clone, Debug, PartialEq, Eq)] -pub struct HighlightConfig { +#[derive(Copy, Clone, Debug)] +pub struct HighlightConfig<'a> { /// Whether to highlight strings pub strings: bool, /// Whether to highlight comments @@ -64,6 +63,7 @@ pub struct HighlightConfig { pub macro_bang: bool, /// Whether to highlight unresolved things be their syntax pub syntactic_name_ref_highlighting: bool, + pub minicore: MiniCore<'a>, } // Feature: Semantic Syntax Highlighting @@ -191,7 +191,7 @@ pub struct HighlightConfig { // ![Semantic Syntax Highlighting](https://user-images.githubusercontent.com/48062697/113187625-f7f50100-9250-11eb-825e-91c58f236071.png) pub(crate) fn highlight( db: &RootDatabase, - config: HighlightConfig, + config: &HighlightConfig<'_>, file_id: FileId, range_to_highlight: Option, ) -> Vec { @@ -226,7 +226,7 @@ pub(crate) fn highlight( fn traverse( hl: &mut Highlights, sema: &Semantics<'_, RootDatabase>, - config: HighlightConfig, + config: &HighlightConfig<'_>, InRealFile { file_id, value: root }: InRealFile<&SyntaxNode>, krate: Option, range_to_highlight: TextRange, @@ -426,12 +426,9 @@ fn traverse( let edition = descended_element.file_id.edition(sema.db); let (unsafe_ops, bindings_shadow_count) = match current_body { Some(current_body) => { - let (ops, bindings) = per_body_cache.entry(current_body).or_insert_with(|| { - ( - hir::attach_db(sema.db, || sema.get_unsafe_ops(current_body)), - Default::default(), - ) - }); + let (ops, bindings) = per_body_cache + .entry(current_body) + .or_insert_with(|| (sema.get_unsafe_ops(current_body), Default::default())); (&*ops, Some(bindings)) } None => (&empty, None), @@ -494,7 +491,7 @@ fn traverse( fn string_injections( hl: &mut Highlights, sema: &Semantics<'_, RootDatabase>, - config: HighlightConfig, + config: &HighlightConfig<'_>, file_id: EditionedFileId, krate: Option, token: SyntaxToken, @@ -591,7 +588,7 @@ fn descend_token( }) } -fn filter_by_config(highlight: &mut Highlight, config: HighlightConfig) -> bool { +fn filter_by_config(highlight: &mut Highlight, config: &HighlightConfig<'_>) -> bool { match &mut highlight.tag { HlTag::StringLiteral if !config.strings => return false, HlTag::Comment if !config.comments => return false, diff --git a/src/tools/rust-analyzer/crates/ide/src/syntax_highlighting/html.rs b/src/tools/rust-analyzer/crates/ide/src/syntax_highlighting/html.rs index 358ac9b4ef35..75e46b8ebfde 100644 --- a/src/tools/rust-analyzer/crates/ide/src/syntax_highlighting/html.rs +++ b/src/tools/rust-analyzer/crates/ide/src/syntax_highlighting/html.rs @@ -1,6 +1,7 @@ //! Renders a bit of code as HTML. use hir::{EditionedFileId, Semantics}; +use ide_db::MiniCore; use oorandom::Rand32; use stdx::format_to; use syntax::AstNode; @@ -12,7 +13,7 @@ use crate::{ pub(crate) fn highlight_as_html_with_config( db: &RootDatabase, - config: HighlightConfig, + config: &HighlightConfig<'_>, file_id: FileId, rainbow: bool, ) -> String { @@ -60,7 +61,7 @@ pub(crate) fn highlight_as_html_with_config( pub(crate) fn highlight_as_html(db: &RootDatabase, file_id: FileId, rainbow: bool) -> String { highlight_as_html_with_config( db, - HighlightConfig { + &HighlightConfig { strings: true, comments: true, punctuation: true, @@ -70,6 +71,7 @@ pub(crate) fn highlight_as_html(db: &RootDatabase, file_id: FileId, rainbow: boo inject_doc_comment: true, macro_bang: true, syntactic_name_ref_highlighting: false, + minicore: MiniCore::default(), }, file_id, rainbow, diff --git a/src/tools/rust-analyzer/crates/ide/src/syntax_highlighting/inject.rs b/src/tools/rust-analyzer/crates/ide/src/syntax_highlighting/inject.rs index efc77823a2a4..7955f5ac0de9 100644 --- a/src/tools/rust-analyzer/crates/ide/src/syntax_highlighting/inject.rs +++ b/src/tools/rust-analyzer/crates/ide/src/syntax_highlighting/inject.rs @@ -4,9 +4,9 @@ use std::mem; use either::Either; use hir::{EditionedFileId, HirFileId, InFile, Semantics, sym}; +use ide_db::range_mapper::RangeMapper; use ide_db::{ - SymbolKind, active_parameter::ActiveParameter, defs::Definition, - documentation::docs_with_rangemap, rust_doc::is_rust_fence, + SymbolKind, defs::Definition, documentation::docs_with_rangemap, rust_doc::is_rust_fence, }; use syntax::{ AstToken, NodeOrToken, SyntaxNode, TextRange, TextSize, @@ -16,85 +16,56 @@ use syntax::{ use crate::{ Analysis, HlMod, HlRange, HlTag, RootDatabase, doc_links::{doc_attributes, extract_definitions_from_docs, resolve_doc_path_for_def}, - syntax_highlighting::{HighlightConfig, highlights::Highlights, injector::Injector}, + syntax_highlighting::{HighlightConfig, highlights::Highlights}, }; pub(super) fn ra_fixture( hl: &mut Highlights, sema: &Semantics<'_, RootDatabase>, - config: HighlightConfig, + config: &HighlightConfig<'_>, literal: &ast::String, expanded: &ast::String, ) -> Option<()> { - let active_parameter = - hir::attach_db(sema.db, || ActiveParameter::at_token(sema, expanded.syntax().clone()))?; - let has_rust_fixture_attr = active_parameter.attrs().is_some_and(|attrs| { - attrs.filter_map(|attr| attr.as_simple_path()).any(|path| { - path.segments() - .zip(["rust_analyzer", "rust_fixture"]) - .all(|(seg, name)| seg.name_ref().map_or(false, |nr| nr.text() == name)) - }) - }); - if !has_rust_fixture_attr { - return None; - } - let value = literal.value().ok()?; + let (analysis, fixture_analysis) = Analysis::from_ra_fixture_with_on_cursor( + sema, + literal.clone(), + expanded, + config.minicore, + &mut |range| { + hl.add(HlRange { + range, + highlight: HlTag::Keyword | HlMod::Injected, + binding_hash: None, + }); + }, + )?; if let Some(range) = literal.open_quote_text_range() { hl.add(HlRange { range, highlight: HlTag::StringLiteral.into(), binding_hash: None }) } - let mut inj = Injector::default(); - - let mut text = &*value; - let mut offset: TextSize = 0.into(); - - while !text.is_empty() { - let marker = "$0"; - let idx = text.find(marker).unwrap_or(text.len()); - let (chunk, next) = text.split_at(idx); - inj.add(chunk, TextRange::at(offset, TextSize::of(chunk))); - - text = next; - offset += TextSize::of(chunk); - - if let Some(next) = text.strip_prefix(marker) { - if let Some(range) = literal.map_range_up(TextRange::at(offset, TextSize::of(marker))) { - hl.add(HlRange { - range, - highlight: HlTag::Keyword | HlMod::Injected, - binding_hash: None, - }); - } - - text = next; - - let marker_len = TextSize::of(marker); - offset += marker_len; - } - } - - let (analysis, tmp_file_id) = Analysis::from_single_file(inj.take_text()); - - for mut hl_range in analysis - .highlight( - HighlightConfig { - syntactic_name_ref_highlighting: false, - comments: true, - punctuation: true, - operator: true, - strings: true, - specialize_punctuation: config.specialize_punctuation, - specialize_operator: config.operator, - inject_doc_comment: config.inject_doc_comment, - macro_bang: config.macro_bang, - }, - tmp_file_id, - ) - .unwrap() - { - for range in inj.map_range_up(hl_range.range) { - if let Some(range) = literal.map_range_up(range) { + for tmp_file_id in fixture_analysis.files() { + for mut hl_range in analysis + .highlight( + HighlightConfig { + syntactic_name_ref_highlighting: false, + comments: true, + punctuation: true, + operator: true, + strings: true, + specialize_punctuation: config.specialize_punctuation, + specialize_operator: config.operator, + inject_doc_comment: config.inject_doc_comment, + macro_bang: config.macro_bang, + // What if there is a fixture inside a fixture? It's fixtures all the way down. + // (In fact, we have a fixture inside a fixture in our test suite!) + minicore: config.minicore, + }, + tmp_file_id, + ) + .unwrap() + { + for range in fixture_analysis.map_range_up(tmp_file_id, hl_range.range) { hl_range.range = range; hl_range.highlight |= HlMod::Injected; hl.add(hl_range); @@ -116,7 +87,7 @@ const RUSTDOC_FENCES: [&str; 2] = ["```", "~~~"]; pub(super) fn doc_comment( hl: &mut Highlights, sema: &Semantics<'_, RootDatabase>, - config: HighlightConfig, + config: &HighlightConfig<'_>, src_file_id: EditionedFileId, node: &SyntaxNode, ) { @@ -128,39 +99,37 @@ pub(super) fn doc_comment( // Extract intra-doc links and emit highlights for them. if let Some((docs, doc_mapping)) = docs_with_rangemap(sema.db, &attributes) { - hir::attach_db(sema.db, || { - extract_definitions_from_docs(&docs) - .into_iter() - .filter_map(|(range, link, ns)| { - doc_mapping - .map(range) - .filter(|(mapping, _)| mapping.file_id == src_file_id) - .and_then(|(InFile { value: mapped_range, .. }, attr_id)| { - Some(mapped_range).zip(resolve_doc_path_for_def( - sema.db, - def, - &link, - ns, - attr_id.is_inner_attr(), - )) - }) - }) - .for_each(|(range, def)| { - hl.add(HlRange { - range, - highlight: module_def_to_hl_tag(def) - | HlMod::Documentation - | HlMod::Injected - | HlMod::IntraDocLink, - binding_hash: None, + extract_definitions_from_docs(&docs) + .into_iter() + .filter_map(|(range, link, ns)| { + doc_mapping + .map(range) + .filter(|(mapping, _)| mapping.file_id == src_file_id) + .and_then(|(InFile { value: mapped_range, .. }, attr_id)| { + Some(mapped_range).zip(resolve_doc_path_for_def( + sema.db, + def, + &link, + ns, + attr_id.is_inner_attr(), + )) }) + }) + .for_each(|(range, def)| { + hl.add(HlRange { + range, + highlight: module_def_to_hl_tag(def) + | HlMod::Documentation + | HlMod::Injected + | HlMod::IntraDocLink, + binding_hash: None, }) - }); + }) } // Extract doc-test sources from the docs and calculate highlighting for them. - let mut inj = Injector::default(); + let mut inj = RangeMapper::default(); inj.add_unmapped("fn doctest() {\n"); let attrs_source_map = attributes.source_map(sema.db); @@ -249,7 +218,7 @@ pub(super) fn doc_comment( if let Ok(ranges) = analysis.with_db(|db| { super::highlight( db, - HighlightConfig { + &HighlightConfig { syntactic_name_ref_highlighting: true, comments: true, punctuation: true, @@ -259,6 +228,7 @@ pub(super) fn doc_comment( specialize_operator: config.operator, inject_doc_comment: config.inject_doc_comment, macro_bang: config.macro_bang, + minicore: config.minicore, }, tmp_file_id, None, diff --git a/src/tools/rust-analyzer/crates/ide/src/syntax_highlighting/injector.rs b/src/tools/rust-analyzer/crates/ide/src/syntax_highlighting/injector.rs deleted file mode 100644 index c30f79732496..000000000000 --- a/src/tools/rust-analyzer/crates/ide/src/syntax_highlighting/injector.rs +++ /dev/null @@ -1,77 +0,0 @@ -//! Extracts a subsequence of a text document, remembering the mapping of ranges -//! between original and extracted texts. -use std::ops::{self, Sub}; - -use stdx::equal_range_by; -use syntax::{TextRange, TextSize}; - -#[derive(Default)] -pub(super) struct Injector { - buf: String, - ranges: Vec<(TextRange, Option>)>, -} - -impl Injector { - pub(super) fn add(&mut self, text: &str, source_range: TextRange) { - let len = TextSize::of(text); - assert_eq!(len, source_range.len()); - self.add_impl(text, Some(source_range.start())); - } - - pub(super) fn add_unmapped(&mut self, text: &str) { - self.add_impl(text, None); - } - - fn add_impl(&mut self, text: &str, source: Option) { - let len = TextSize::of(text); - let target_range = TextRange::at(TextSize::of(&self.buf), len); - self.ranges.push((target_range, source.map(|it| Delta::new(target_range.start(), it)))); - self.buf.push_str(text); - } - - pub(super) fn take_text(&mut self) -> String { - std::mem::take(&mut self.buf) - } - - pub(super) fn map_range_up(&self, range: TextRange) -> impl Iterator + '_ { - equal_range_by(&self.ranges, |&(r, _)| TextRange::ordering(r, range)).filter_map(move |i| { - let (target_range, delta) = self.ranges[i]; - let intersection = target_range.intersect(range).unwrap(); - Some(intersection + delta?) - }) - } -} - -#[derive(Clone, Copy)] -enum Delta { - Add(T), - Sub(T), -} - -impl Delta { - fn new(from: T, to: T) -> Delta - where - T: Ord + Sub, - { - if to >= from { Delta::Add(to - from) } else { Delta::Sub(from - to) } - } -} - -impl ops::Add> for TextSize { - type Output = TextSize; - - fn add(self, rhs: Delta) -> TextSize { - match rhs { - Delta::Add(it) => self + it, - Delta::Sub(it) => self - it, - } - } -} - -impl ops::Add> for TextRange { - type Output = TextRange; - - fn add(self, rhs: Delta) -> TextRange { - TextRange::at(self.start() + rhs, self.len()) - } -} diff --git a/src/tools/rust-analyzer/crates/ide/src/syntax_highlighting/test_data/highlight_injection.html b/src/tools/rust-analyzer/crates/ide/src/syntax_highlighting/test_data/highlight_injection.html index 3b468ab6dba6..579c6ceadcb8 100644 --- a/src/tools/rust-analyzer/crates/ide/src/syntax_highlighting/test_data/highlight_injection.html +++ b/src/tools/rust-analyzer/crates/ide/src/syntax_highlighting/test_data/highlight_injection.html @@ -43,18 +43,19 @@ pre { color: #DCDCCC; background: #3F3F3F; font-size: 22px; padd
fn fixture(#[rust_analyzer::rust_fixture] ra_fixture: &str) {}
 
 fn main() {
-    fixture(r#"
-trait Foo {
-    fn foo() {
-        println!("2 + 2 = {}", 4);
-    }
+    fixture(r#"
+@@- minicore: sized
+trait Foo: Sized {
+    fn foo() {
+        println!("2 + 2 = {}", 4);
+    }
 }"#
     );
-    fixture(r"
-fn foo() {
-    foo($0{
-        92
-    }$0)
+    fixture(r"
+fn foo() {
+    foo($0{
+        92
+    }$0)
 }"
     );
 }
\ No newline at end of file diff --git a/src/tools/rust-analyzer/crates/ide/src/syntax_highlighting/test_data/highlight_injection_2.html b/src/tools/rust-analyzer/crates/ide/src/syntax_highlighting/test_data/highlight_injection_2.html new file mode 100644 index 000000000000..fc2d9a387016 --- /dev/null +++ b/src/tools/rust-analyzer/crates/ide/src/syntax_highlighting/test_data/highlight_injection_2.html @@ -0,0 +1,61 @@ + + +
fn fixture(#[rust_analyzer::rust_fixture] ra_fixture: &str) {}
+
+fn main() {
+    fixture(r#"
+@@- /main.rs crate:main deps:other_crate
+fn test() {
+    let x = other_crate::foo::S::thing();
+    x;
+} //^ i128
+
+@@- /lib.rs crate:other_crate
+pub mod foo {
+    pub struct S;
+    impl S {
+        pub fn thing() -> i128 { 0 }
+    }
+}
+    "#);
+}
\ No newline at end of file diff --git a/src/tools/rust-analyzer/crates/ide/src/syntax_highlighting/tests.rs b/src/tools/rust-analyzer/crates/ide/src/syntax_highlighting/tests.rs index 8198701d6843..4e84127c29f8 100644 --- a/src/tools/rust-analyzer/crates/ide/src/syntax_highlighting/tests.rs +++ b/src/tools/rust-analyzer/crates/ide/src/syntax_highlighting/tests.rs @@ -1,13 +1,13 @@ use std::time::Instant; use expect_test::{ExpectFile, expect_file}; -use ide_db::SymbolKind; +use ide_db::{MiniCore, SymbolKind}; use span::Edition; use test_utils::{AssertLinear, bench, bench_fixture, skip_slow_tests}; use crate::{FileRange, HighlightConfig, HlTag, TextRange, fixture}; -const HL_CONFIG: HighlightConfig = HighlightConfig { +const HL_CONFIG: HighlightConfig<'_> = HighlightConfig { strings: true, comments: true, punctuation: true, @@ -17,6 +17,7 @@ const HL_CONFIG: HighlightConfig = HighlightConfig { inject_doc_comment: true, macro_bang: true, syntactic_name_ref_highlighting: false, + minicore: MiniCore::default(), }; #[test] @@ -1016,6 +1017,35 @@ impl t for foo { ) } +#[test] +fn test_injection_2() { + check_highlighting( + r##" +fn fixture(#[rust_analyzer::rust_fixture] ra_fixture: &str) {} + +fn main() { + fixture(r#" +@@- /main.rs crate:main deps:other_crate +fn test() { + let x = other_crate::foo::S::thing(); + x; +} //^ i128 + +@@- /lib.rs crate:other_crate +pub mod foo { + pub struct S; + impl S { + pub fn thing() -> i128 { 0 } + } +} + "#); +} +"##, + expect_file!["./test_data/highlight_injection_2.html"], + false, + ); +} + #[test] fn test_injection() { check_highlighting( @@ -1024,7 +1054,8 @@ fn fixture(#[rust_analyzer::rust_fixture] ra_fixture: &str) {} fn main() { fixture(r#" -trait Foo { +@@- minicore: sized +trait Foo: Sized { fn foo() { println!("2 + 2 = {}", 4); } @@ -1223,7 +1254,7 @@ fn foo(x: &fn(&dyn Trait)) {} /// Note that the `snapshot` file is overwritten by the rendered HTML. fn check_highlighting_with_config( #[rust_analyzer::rust_fixture] ra_fixture: &str, - config: HighlightConfig, + config: HighlightConfig<'_>, expect: ExpectFile, rainbow: bool, ) { diff --git a/src/tools/rust-analyzer/crates/macros/src/lib.rs b/src/tools/rust-analyzer/crates/macros/src/lib.rs index 8bafcf498c51..3f90ecc8f902 100644 --- a/src/tools/rust-analyzer/crates/macros/src/lib.rs +++ b/src/tools/rust-analyzer/crates/macros/src/lib.rs @@ -162,3 +162,42 @@ fn has_ignore_attr(attrs: &[syn::Attribute], name: &'static str, meta: &'static ignored } + +decl_derive!( + [UpmapFromRaFixture] => upmap_from_ra_fixture +); + +fn upmap_from_ra_fixture(mut s: synstructure::Structure<'_>) -> proc_macro2::TokenStream { + if let syn::Data::Union(_) = s.ast().data { + panic!("cannot derive on union") + } + + s.add_bounds(synstructure::AddBounds::Generics); + s.bind_with(|_| synstructure::BindStyle::Move); + let body = s.each_variant(|vi| { + let bindings = vi.bindings(); + vi.construct(|_, index| { + let bind = &bindings[index]; + + quote! { + ::ide_db::ra_fixture::UpmapFromRaFixture::upmap_from_ra_fixture( + #bind, __analysis, __virtual_file_id, __real_file_id, + )? + } + }) + }); + + s.bound_impl( + quote!(::ide_db::ra_fixture::UpmapFromRaFixture), + quote! { + fn upmap_from_ra_fixture( + self, + __analysis: &::ide_db::ra_fixture::RaFixtureAnalysis, + __virtual_file_id: ::ide_db::ra_fixture::FileId, + __real_file_id: ::ide_db::ra_fixture::FileId, + ) -> Result { + Ok(match self { #body }) + } + }, + ) +} diff --git a/src/tools/rust-analyzer/crates/rust-analyzer/src/cli/analysis_stats.rs b/src/tools/rust-analyzer/crates/rust-analyzer/src/cli/analysis_stats.rs index 2a9ef981291e..717bd230a21e 100644 --- a/src/tools/rust-analyzer/crates/rust-analyzer/src/cli/analysis_stats.rs +++ b/src/tools/rust-analyzer/crates/rust-analyzer/src/cli/analysis_stats.rs @@ -25,7 +25,7 @@ use ide::{ InlayHintsConfig, LineCol, RootDatabase, }; use ide_db::{ - EditionedFileId, LineIndexDatabase, SnippetCap, + EditionedFileId, LineIndexDatabase, MiniCore, SnippetCap, base_db::{SourceDatabase, salsa::Database}, }; use itertools::Itertools; @@ -1194,6 +1194,7 @@ impl flags::AnalysisStats { closing_brace_hints_min_lines: Some(20), fields_to_resolve: InlayFieldsToResolve::empty(), range_exclusive_hints: true, + minicore: MiniCore::default(), }, analysis.editioned_file_id_to_vfs(file_id), None, @@ -1203,26 +1204,25 @@ impl flags::AnalysisStats { bar.finish_and_clear(); let mut bar = create_bar(); + let annotation_config = AnnotationConfig { + binary_target: true, + annotate_runnables: true, + annotate_impls: true, + annotate_references: false, + annotate_method_references: false, + annotate_enum_variant_references: false, + location: ide::AnnotationLocation::AboveName, + minicore: MiniCore::default(), + }; for &file_id in file_ids { let msg = format!("annotations: {}", vfs.file_path(file_id.file_id(db))); bar.set_message(move || msg.clone()); analysis - .annotations( - &AnnotationConfig { - binary_target: true, - annotate_runnables: true, - annotate_impls: true, - annotate_references: false, - annotate_method_references: false, - annotate_enum_variant_references: false, - location: ide::AnnotationLocation::AboveName, - }, - analysis.editioned_file_id_to_vfs(file_id), - ) + .annotations(&annotation_config, analysis.editioned_file_id_to_vfs(file_id)) .unwrap() .into_iter() .for_each(|annotation| { - _ = analysis.resolve_annotation(annotation); + _ = analysis.resolve_annotation(&annotation_config, annotation); }); bar.inc(1); } diff --git a/src/tools/rust-analyzer/crates/rust-analyzer/src/config.rs b/src/tools/rust-analyzer/crates/rust-analyzer/src/config.rs index 96b65838ae42..652c2e32ffa6 100644 --- a/src/tools/rust-analyzer/crates/rust-analyzer/src/config.rs +++ b/src/tools/rust-analyzer/crates/rust-analyzer/src/config.rs @@ -8,14 +8,14 @@ use std::{env, fmt, iter, ops::Not, sync::OnceLock}; use cfg::{CfgAtom, CfgDiff}; use hir::Symbol; use ide::{ - AssistConfig, CallHierarchyConfig, CallableSnippets, CompletionConfig, - CompletionFieldsToResolve, DiagnosticsConfig, GenericParameterHints, HighlightConfig, - HighlightRelatedConfig, HoverConfig, HoverDocFormat, InlayFieldsToResolve, InlayHintsConfig, - JoinLinesConfig, MemoryLayoutHoverConfig, MemoryLayoutHoverRenderKind, Snippet, SnippetScope, - SourceRootId, + AnnotationConfig, AssistConfig, CallHierarchyConfig, CallableSnippets, CompletionConfig, + CompletionFieldsToResolve, DiagnosticsConfig, GenericParameterHints, GotoDefinitionConfig, + HighlightConfig, HighlightRelatedConfig, HoverConfig, HoverDocFormat, InlayFieldsToResolve, + InlayHintsConfig, JoinLinesConfig, MemoryLayoutHoverConfig, MemoryLayoutHoverRenderKind, + Snippet, SnippetScope, SourceRootId, }; use ide_db::{ - SnippetCap, + MiniCore, SnippetCap, assists::ExprFillDefaultMode, imports::insert_use::{ImportGranularity, InsertUseConfig, PrefixKind}, }; @@ -1454,6 +1454,23 @@ impl LensConfig { pub fn references(&self) -> bool { self.method_refs || self.refs_adt || self.refs_trait || self.enum_variant_refs } + + pub fn into_annotation_config<'a>( + self, + binary_target: bool, + minicore: MiniCore<'a>, + ) -> AnnotationConfig<'a> { + AnnotationConfig { + binary_target, + annotate_runnables: self.runnable(), + annotate_impls: self.implementations, + annotate_references: self.refs_adt, + annotate_method_references: self.method_refs, + annotate_enum_variant_references: self.enum_variant_refs, + location: self.location.into(), + minicore, + } + } } #[derive(Clone, Debug, PartialEq, Eq)] @@ -1688,11 +1705,15 @@ impl Config { } } - pub fn call_hierarchy(&self) -> CallHierarchyConfig { - CallHierarchyConfig { exclude_tests: self.references_excludeTests().to_owned() } + pub fn call_hierarchy<'a>(&self, minicore: MiniCore<'a>) -> CallHierarchyConfig<'a> { + CallHierarchyConfig { exclude_tests: self.references_excludeTests().to_owned(), minicore } } - pub fn completion(&self, source_root: Option) -> CompletionConfig<'_> { + pub fn completion<'a>( + &'a self, + source_root: Option, + minicore: MiniCore<'a>, + ) -> CompletionConfig<'a> { let client_capability_fields = self.completion_resolve_support_properties(); CompletionConfig { enable_postfix_completions: self.completion_postfix_enable(source_root).to_owned(), @@ -1746,6 +1767,7 @@ impl Config { }) .collect(), exclude_traits: self.completion_excludeTraits(source_root), + minicore, } } @@ -1820,7 +1842,7 @@ impl Config { } } - pub fn hover(&self) -> HoverConfig { + pub fn hover<'a>(&self, minicore: MiniCore<'a>) -> HoverConfig<'a> { let mem_kind = |kind| match kind { MemoryLayoutHoverRenderKindDef::Both => MemoryLayoutHoverRenderKind::Both, MemoryLayoutHoverRenderKindDef::Decimal => MemoryLayoutHoverRenderKind::Decimal, @@ -1853,10 +1875,15 @@ impl Config { None => ide::SubstTyLen::Unlimited, }, show_drop_glue: *self.hover_dropGlue_enable(), + minicore, } } - pub fn inlay_hints(&self) -> InlayHintsConfig { + pub fn goto_definition<'a>(&self, minicore: MiniCore<'a>) -> GotoDefinitionConfig<'a> { + GotoDefinitionConfig { minicore } + } + + pub fn inlay_hints<'a>(&self, minicore: MiniCore<'a>) -> InlayHintsConfig<'a> { let client_capability_fields = self.inlay_hint_resolve_support_properties(); InlayHintsConfig { @@ -1938,6 +1965,7 @@ impl Config { ), implicit_drop_hints: self.inlayHints_implicitDrops_enable().to_owned(), range_exclusive_hints: self.inlayHints_rangeExclusiveHints_enable().to_owned(), + minicore, } } @@ -1975,7 +2003,7 @@ impl Config { self.semanticHighlighting_nonStandardTokens().to_owned() } - pub fn highlighting_config(&self) -> HighlightConfig { + pub fn highlighting_config<'a>(&self, minicore: MiniCore<'a>) -> HighlightConfig<'a> { HighlightConfig { strings: self.semanticHighlighting_strings_enable().to_owned(), comments: self.semanticHighlighting_comments_enable().to_owned(), @@ -1990,6 +2018,7 @@ impl Config { .to_owned(), inject_doc_comment: self.semanticHighlighting_doc_comment_inject_enable().to_owned(), syntactic_name_ref_highlighting: false, + minicore, } } diff --git a/src/tools/rust-analyzer/crates/rust-analyzer/src/global_state.rs b/src/tools/rust-analyzer/crates/rust-analyzer/src/global_state.rs index ce6644f725ca..f557dd5cb092 100644 --- a/src/tools/rust-analyzer/crates/rust-analyzer/src/global_state.rs +++ b/src/tools/rust-analyzer/crates/rust-analyzer/src/global_state.rs @@ -13,7 +13,10 @@ use cargo_metadata::PackageId; use crossbeam_channel::{Receiver, Sender, unbounded}; use hir::ChangeWithProcMacros; use ide::{Analysis, AnalysisHost, Cancellable, FileId, SourceRootId}; -use ide_db::base_db::{Crate, ProcMacroPaths, SourceDatabase}; +use ide_db::{ + MiniCore, + base_db::{Crate, ProcMacroPaths, SourceDatabase}, +}; use itertools::Itertools; use load_cargo::SourceRootConfig; use lsp_types::{SemanticTokens, Url}; @@ -188,6 +191,14 @@ pub(crate) struct GlobalState { /// This is marked true if we failed to load a crate root file at crate graph creation, /// which will usually end up causing a bunch of incorrect diagnostics on startup. pub(crate) incomplete_crate_graph: bool, + + pub(crate) minicore: MiniCoreRustAnalyzerInternalOnly, +} + +// FIXME: This should move to the VFS once the rewrite is done. +#[derive(Debug, Clone, Default)] +pub(crate) struct MiniCoreRustAnalyzerInternalOnly { + pub(crate) minicore_text: Option, } /// An immutable snapshot of the world's state at a point in time. @@ -204,6 +215,7 @@ pub(crate) struct GlobalStateSnapshot { // FIXME: Can we derive this from somewhere else? pub(crate) proc_macros_loaded: bool, pub(crate) flycheck: Arc<[FlycheckHandle]>, + minicore: MiniCoreRustAnalyzerInternalOnly, } impl std::panic::UnwindSafe for GlobalStateSnapshot {} @@ -304,6 +316,8 @@ impl GlobalState { deferred_task_queue: task_queue, incomplete_crate_graph: false, + + minicore: MiniCoreRustAnalyzerInternalOnly::default(), }; // Apply any required database inputs from the config. this.update_configuration(config); @@ -550,6 +564,7 @@ impl GlobalState { workspaces: Arc::clone(&self.workspaces), analysis: self.analysis_host.analysis(), vfs: Arc::clone(&self.vfs), + minicore: self.minicore.clone(), check_fixes: Arc::clone(&self.diagnostics.check_fixes), mem_docs: self.mem_docs.clone(), semantic_tokens_cache: Arc::clone(&self.semantic_tokens_cache), @@ -838,6 +853,14 @@ impl GlobalStateSnapshot { pub(crate) fn file_exists(&self, file_id: FileId) -> bool { self.vfs.read().0.exists(file_id) } + + #[inline] + pub(crate) fn minicore(&self) -> MiniCore<'_> { + match &self.minicore.minicore_text { + Some(minicore) => MiniCore::new(minicore), + None => MiniCore::default(), + } + } } pub(crate) fn file_id_to_url(vfs: &vfs::Vfs, id: FileId) -> Url { diff --git a/src/tools/rust-analyzer/crates/rust-analyzer/src/handlers/request.rs b/src/tools/rust-analyzer/crates/rust-analyzer/src/handlers/request.rs index 6cb28aecf748..55d092f30f6b 100644 --- a/src/tools/rust-analyzer/crates/rust-analyzer/src/handlers/request.rs +++ b/src/tools/rust-analyzer/crates/rust-analyzer/src/handlers/request.rs @@ -7,8 +7,8 @@ use anyhow::Context; use base64::{Engine, prelude::BASE64_STANDARD}; use ide::{ - AnnotationConfig, AssistKind, AssistResolveStrategy, Cancellable, CompletionFieldsToResolve, - FilePosition, FileRange, FileStructureConfig, HoverAction, HoverGotoTypeData, + AssistKind, AssistResolveStrategy, Cancellable, CompletionFieldsToResolve, FilePosition, + FileRange, FileStructureConfig, FindAllRefsConfig, HoverAction, HoverGotoTypeData, InlayFieldsToResolve, Query, RangeInfo, ReferenceCategory, Runnable, RunnableKind, SingleResolve, SourceChange, TextEdit, }; @@ -811,7 +811,8 @@ pub(crate) fn handle_goto_definition( let _p = tracing::info_span!("handle_goto_definition").entered(); let position = try_default!(from_proto::file_position(&snap, params.text_document_position_params)?); - let nav_info = match snap.analysis.goto_definition(position)? { + let config = snap.config.goto_definition(snap.minicore()); + let nav_info = match snap.analysis.goto_definition(position, &config)? { None => return Ok(None), Some(it) => it, }; @@ -829,7 +830,8 @@ pub(crate) fn handle_goto_declaration( &snap, params.text_document_position_params.clone() )?); - let nav_info = match snap.analysis.goto_declaration(position)? { + let config = snap.config.goto_definition(snap.minicore()); + let nav_info = match snap.analysis.goto_declaration(position, &config)? { None => return handle_goto_definition(snap, params), Some(it) => it, }; @@ -1106,7 +1108,7 @@ pub(crate) fn handle_completion( context.and_then(|ctx| ctx.trigger_character).and_then(|s| s.chars().next()); let source_root = snap.analysis.source_root_id(position.file_id)?; - let completion_config = &snap.config.completion(Some(source_root)); + let completion_config = &snap.config.completion(Some(source_root), snap.minicore()); // FIXME: We should fix up the position when retrying the cancelled request instead position.offset = position.offset.min(line_index.index.len()); let items = match snap.analysis.completions( @@ -1160,7 +1162,8 @@ pub(crate) fn handle_completion_resolve( }; let source_root = snap.analysis.source_root_id(file_id)?; - let mut forced_resolve_completions_config = snap.config.completion(Some(source_root)); + let mut forced_resolve_completions_config = + snap.config.completion(Some(source_root), snap.minicore()); forced_resolve_completions_config.fields_to_resolve = CompletionFieldsToResolve::empty(); let position = FilePosition { file_id, offset }; @@ -1274,7 +1277,7 @@ pub(crate) fn handle_hover( }; let file_range = try_default!(from_proto::file_range(&snap, ¶ms.text_document, range)?); - let hover = snap.config.hover(); + let hover = snap.config.hover(snap.minicore()); let info = match snap.analysis.hover(&hover, file_range)? { None => return Ok(None), Some(info) => info, @@ -1360,7 +1363,11 @@ pub(crate) fn handle_references( let exclude_imports = snap.config.find_all_refs_exclude_imports(); let exclude_tests = snap.config.find_all_refs_exclude_tests(); - let Some(refs) = snap.analysis.find_all_refs(position, None)? else { + let Some(refs) = snap.analysis.find_all_refs( + position, + &FindAllRefsConfig { search_scope: None, minicore: snap.minicore() }, + )? + else { return Ok(None); }; @@ -1615,8 +1622,8 @@ pub(crate) fn handle_code_lens( let target_spec = TargetSpec::for_file(&snap, file_id)?; let annotations = snap.analysis.annotations( - &AnnotationConfig { - binary_target: target_spec + &lens_config.into_annotation_config( + target_spec .map(|spec| { matches!( spec.target_kind(), @@ -1624,13 +1631,8 @@ pub(crate) fn handle_code_lens( ) }) .unwrap_or(false), - annotate_runnables: lens_config.runnable(), - annotate_impls: lens_config.implementations, - annotate_references: lens_config.refs_adt, - annotate_method_references: lens_config.method_refs, - annotate_enum_variant_references: lens_config.enum_variant_refs, - location: lens_config.location.into(), - }, + snap.minicore(), + ), file_id, )?; @@ -1653,7 +1655,8 @@ pub(crate) fn handle_code_lens_resolve( let Some(annotation) = from_proto::annotation(&snap, code_lens.range, resolve)? else { return Ok(code_lens); }; - let annotation = snap.analysis.resolve_annotation(annotation)?; + let config = snap.config.lens().into_annotation_config(false, snap.minicore()); + let annotation = snap.analysis.resolve_annotation(&config, annotation)?; let mut acc = Vec::new(); to_proto::code_lens(&mut acc, &snap, annotation)?; @@ -1736,7 +1739,7 @@ pub(crate) fn handle_inlay_hints( range.end().min(line_index.index.len()), ); - let inlay_hints_config = snap.config.inlay_hints(); + let inlay_hints_config = snap.config.inlay_hints(snap.minicore()); Ok(Some( snap.analysis .inlay_hints(&inlay_hints_config, file_id, Some(range))? @@ -1777,7 +1780,7 @@ pub(crate) fn handle_inlay_hints_resolve( let line_index = snap.file_line_index(file_id)?; let range = from_proto::text_range(&line_index, resolve_data.resolve_range)?; - let mut forced_resolve_inlay_hints_config = snap.config.inlay_hints(); + let mut forced_resolve_inlay_hints_config = snap.config.inlay_hints(snap.minicore()); forced_resolve_inlay_hints_config.fields_to_resolve = InlayFieldsToResolve::empty(); let resolve_hints = snap.analysis.inlay_hints_resolve( &forced_resolve_inlay_hints_config, @@ -1816,7 +1819,8 @@ pub(crate) fn handle_call_hierarchy_prepare( let position = try_default!(from_proto::file_position(&snap, params.text_document_position_params)?); - let nav_info = match snap.analysis.call_hierarchy(position)? { + let config = snap.config.call_hierarchy(snap.minicore()); + let nav_info = match snap.analysis.call_hierarchy(position, &config)? { None => return Ok(None), Some(it) => it, }; @@ -1842,8 +1846,8 @@ pub(crate) fn handle_call_hierarchy_incoming( let frange = try_default!(from_proto::file_range(&snap, &doc, item.selection_range)?); let fpos = FilePosition { file_id: frange.file_id, offset: frange.range.start() }; - let config = snap.config.call_hierarchy(); - let call_items = match snap.analysis.incoming_calls(config, fpos)? { + let config = snap.config.call_hierarchy(snap.minicore()); + let call_items = match snap.analysis.incoming_calls(&config, fpos)? { None => return Ok(None), Some(it) => it, }; @@ -1881,8 +1885,8 @@ pub(crate) fn handle_call_hierarchy_outgoing( let fpos = FilePosition { file_id: frange.file_id, offset: frange.range.start() }; let line_index = snap.file_line_index(fpos.file_id)?; - let config = snap.config.call_hierarchy(); - let call_items = match snap.analysis.outgoing_calls(config, fpos)? { + let config = snap.config.call_hierarchy(snap.minicore()); + let call_items = match snap.analysis.outgoing_calls(&config, fpos)? { None => return Ok(None), Some(it) => it, }; @@ -1916,7 +1920,7 @@ pub(crate) fn handle_semantic_tokens_full( let text = snap.analysis.file_text(file_id)?; let line_index = snap.file_line_index(file_id)?; - let mut highlight_config = snap.config.highlighting_config(); + let mut highlight_config = snap.config.highlighting_config(snap.minicore()); // Avoid flashing a bunch of unresolved references when the proc-macro servers haven't been spawned yet. highlight_config.syntactic_name_ref_highlighting = snap.workspaces.is_empty() || !snap.proc_macros_loaded; @@ -1946,7 +1950,7 @@ pub(crate) fn handle_semantic_tokens_full_delta( let text = snap.analysis.file_text(file_id)?; let line_index = snap.file_line_index(file_id)?; - let mut highlight_config = snap.config.highlighting_config(); + let mut highlight_config = snap.config.highlighting_config(snap.minicore()); // Avoid flashing a bunch of unresolved references when the proc-macro servers haven't been spawned yet. highlight_config.syntactic_name_ref_highlighting = snap.workspaces.is_empty() || !snap.proc_macros_loaded; @@ -1988,7 +1992,7 @@ pub(crate) fn handle_semantic_tokens_range( let text = snap.analysis.file_text(frange.file_id)?; let line_index = snap.file_line_index(frange.file_id)?; - let mut highlight_config = snap.config.highlighting_config(); + let mut highlight_config = snap.config.highlighting_config(snap.minicore()); // Avoid flashing a bunch of unresolved references when the proc-macro servers haven't been spawned yet. highlight_config.syntactic_name_ref_highlighting = snap.workspaces.is_empty() || !snap.proc_macros_loaded; @@ -2156,7 +2160,13 @@ fn show_ref_command_link( ) -> Option { if snap.config.hover_actions().references && snap.config.client_commands().show_reference - && let Some(ref_search_res) = snap.analysis.find_all_refs(*position, None).unwrap_or(None) + && let Some(ref_search_res) = snap + .analysis + .find_all_refs( + *position, + &FindAllRefsConfig { search_scope: None, minicore: snap.minicore() }, + ) + .unwrap_or(None) { let uri = to_proto::url(snap, position.file_id); let line_index = snap.file_line_index(position.file_id).ok()?; diff --git a/src/tools/rust-analyzer/crates/rust-analyzer/src/integrated_benchmarks.rs b/src/tools/rust-analyzer/crates/rust-analyzer/src/integrated_benchmarks.rs index 84b7888258f8..38ee9cbe7fc8 100644 --- a/src/tools/rust-analyzer/crates/rust-analyzer/src/integrated_benchmarks.rs +++ b/src/tools/rust-analyzer/crates/rust-analyzer/src/integrated_benchmarks.rs @@ -16,7 +16,7 @@ use ide::{ FilePosition, TextSize, }; use ide_db::{ - SnippetCap, + MiniCore, SnippetCap, imports::insert_use::{ImportGranularity, InsertUseConfig}, }; use project_model::CargoConfig; @@ -186,6 +186,7 @@ fn integrated_completion_benchmark() { exclude_traits: &[], enable_auto_await: true, enable_auto_iter: true, + minicore: MiniCore::default(), }; let position = FilePosition { file_id, offset: TextSize::try_from(completion_offset).unwrap() }; @@ -240,6 +241,7 @@ fn integrated_completion_benchmark() { exclude_traits: &[], enable_auto_await: true, enable_auto_iter: true, + minicore: MiniCore::default(), }; let position = FilePosition { file_id, offset: TextSize::try_from(completion_offset).unwrap() }; @@ -292,6 +294,7 @@ fn integrated_completion_benchmark() { exclude_traits: &[], enable_auto_await: true, enable_auto_iter: true, + minicore: MiniCore::default(), }; let position = FilePosition { file_id, offset: TextSize::try_from(completion_offset).unwrap() }; diff --git a/src/tools/rust-analyzer/crates/rust-analyzer/src/lsp/to_proto.rs b/src/tools/rust-analyzer/crates/rust-analyzer/src/lsp/to_proto.rs index d51ddb86d197..cd384ca713ec 100644 --- a/src/tools/rust-analyzer/crates/rust-analyzer/src/lsp/to_proto.rs +++ b/src/tools/rust-analyzer/crates/rust-analyzer/src/lsp/to_proto.rs @@ -16,7 +16,9 @@ use ide::{ SnippetEdit, SourceChange, StructureNodeKind, SymbolKind, TextEdit, TextRange, TextSize, UpdateTest, }; -use ide_db::{FxHasher, assists, rust_doc::format_docs, source_change::ChangeAnnotationId}; +use ide_db::{ + FxHasher, MiniCore, assists, rust_doc::format_docs, source_change::ChangeAnnotationId, +}; use itertools::Itertools; use paths::{Utf8Component, Utf8Prefix}; use semver::VersionReq; @@ -270,7 +272,7 @@ pub(crate) fn completion_items( ); } - if let Some(limit) = config.completion(None).limit { + if let Some(limit) = config.completion(None, MiniCore::default()).limit { res.sort_by(|item1, item2| item1.sort_text.cmp(&item2.sort_text)); res.truncate(limit); } @@ -400,16 +402,17 @@ fn completion_item( set_score(&mut lsp_item, max_relevance, item.relevance); - let imports = - if config.completion(None).enable_imports_on_the_fly && !item.import_to_add.is_empty() { - item.import_to_add - .clone() - .into_iter() - .map(|import_path| lsp_ext::CompletionImport { full_import_path: import_path }) - .collect() - } else { - Vec::new() - }; + let imports = if config.completion(None, MiniCore::default()).enable_imports_on_the_fly + && !item.import_to_add.is_empty() + { + item.import_to_add + .clone() + .into_iter() + .map(|import_path| lsp_ext::CompletionImport { full_import_path: import_path }) + .collect() + } else { + Vec::new() + }; let (ref_resolve_data, resolve_data) = if something_to_resolve || !imports.is_empty() { let ref_resolve_data = if ref_match.is_some() { let ref_resolve_data = lsp_ext::CompletionResolveData { diff --git a/src/tools/rust-analyzer/crates/rust-analyzer/src/main_loop.rs b/src/tools/rust-analyzer/crates/rust-analyzer/src/main_loop.rs index 3e80e8b7bdfb..c0947b2a291e 100644 --- a/src/tools/rust-analyzer/crates/rust-analyzer/src/main_loop.rs +++ b/src/tools/rust-analyzer/crates/rust-analyzer/src/main_loop.rs @@ -847,6 +847,13 @@ impl GlobalState { self.debounce_workspace_fetch(); let vfs = &mut self.vfs.write().0; for (path, contents) in files { + if matches!(path.name_and_extension(), Some(("minicore", Some("rs")))) { + // Not a lot of bad can happen from mistakenly identifying `minicore`, so proceed with that. + self.minicore.minicore_text = contents + .as_ref() + .and_then(|contents| String::from_utf8(contents.clone()).ok()); + } + let path = VfsPath::from(path); // if the file is in mem docs, it's managed by the client via notifications // so only set it if its not in there diff --git a/src/tools/rust-analyzer/crates/syntax/src/ast/token_ext.rs b/src/tools/rust-analyzer/crates/syntax/src/ast/token_ext.rs index d9223e8216da..e1a9f3ac0341 100644 --- a/src/tools/rust-analyzer/crates/syntax/src/ast/token_ext.rs +++ b/src/tools/rust-analyzer/crates/syntax/src/ast/token_ext.rs @@ -201,6 +201,10 @@ pub trait IsString: AstToken { None } } + fn map_offset_down(&self, offset: TextSize) -> Option { + let contents_range = self.text_range_between_quotes()?; + offset.checked_sub(contents_range.start()) + } } impl IsString for ast::String { diff --git a/src/tools/rust-analyzer/crates/test-fixture/src/lib.rs b/src/tools/rust-analyzer/crates/test-fixture/src/lib.rs index a4549794db33..aefe81f83e29 100644 --- a/src/tools/rust-analyzer/crates/test-fixture/src/lib.rs +++ b/src/tools/rust-analyzer/crates/test-fixture/src/lib.rs @@ -24,7 +24,7 @@ use paths::AbsPathBuf; use span::{Edition, FileId, Span}; use stdx::itertools::Itertools; use test_utils::{ - CURSOR_MARKER, ESCAPED_CURSOR_MARKER, Fixture, FixtureWithProjectMeta, RangeOrOffset, + CURSOR_MARKER, ESCAPED_CURSOR_MARKER, Fixture, FixtureWithProjectMeta, MiniCore, RangeOrOffset, extract_range_or_offset, }; use triomphe::Arc; @@ -69,7 +69,12 @@ pub trait WithFixture: Default + ExpandDatabase + SourceDatabase + 'static { proc_macros: Vec<(String, ProcMacro)>, ) -> Self { let mut db = Self::default(); - let fixture = ChangeFixture::parse_with_proc_macros(&db, ra_fixture, proc_macros); + let fixture = ChangeFixture::parse_with_proc_macros( + &db, + ra_fixture, + MiniCore::RAW_SOURCE, + proc_macros, + ); fixture.change.apply(&mut db); assert!(fixture.file_position.is_none()); db @@ -112,8 +117,10 @@ impl WithFixture for DB pub struct ChangeFixture { pub file_position: Option<(EditionedFileId, RangeOrOffset)>, + pub file_lines: Vec, pub files: Vec, pub change: ChangeWithProcMacros, + pub sysroot_files: Vec, } const SOURCE_ROOT_PREFIX: &str = "/"; @@ -123,12 +130,13 @@ impl ChangeFixture { db: &dyn salsa::Database, #[rust_analyzer::rust_fixture] ra_fixture: &str, ) -> ChangeFixture { - Self::parse_with_proc_macros(db, ra_fixture, Vec::new()) + Self::parse_with_proc_macros(db, ra_fixture, MiniCore::RAW_SOURCE, Vec::new()) } pub fn parse_with_proc_macros( db: &dyn salsa::Database, #[rust_analyzer::rust_fixture] ra_fixture: &str, + minicore_raw: &str, mut proc_macro_defs: Vec<(String, ProcMacro)>, ) -> ChangeFixture { let FixtureWithProjectMeta { @@ -149,6 +157,8 @@ impl ChangeFixture { let mut source_change = FileChange::default(); let mut files = Vec::new(); + let mut sysroot_files = Vec::new(); + let mut file_lines = Vec::new(); let mut crate_graph = CrateGraphBuilder::default(); let mut crates = FxIndexMap::default(); let mut crate_deps = Vec::new(); @@ -173,6 +183,8 @@ impl ChangeFixture { let proc_macro_cwd = Arc::new(AbsPathBuf::assert_utf8(std::env::current_dir().unwrap())); for entry in fixture { + file_lines.push(entry.line); + let mut range_or_offset = None; let text = if entry.text.contains(CURSOR_MARKER) { if entry.text.contains(ESCAPED_CURSOR_MARKER) { @@ -259,7 +271,9 @@ impl ChangeFixture { fs.insert(core_file, VfsPath::new_virtual_path("/sysroot/core/lib.rs".to_owned())); roots.push(SourceRoot::new_library(fs)); - source_change.change_file(core_file, Some(mini_core.source_code())); + sysroot_files.push(core_file); + + source_change.change_file(core_file, Some(mini_core.source_code(minicore_raw))); let core_crate = crate_graph.add_crate_root( core_file, @@ -348,6 +362,8 @@ impl ChangeFixture { ); roots.push(SourceRoot::new_library(fs)); + sysroot_files.push(proc_lib_file); + source_change.change_file(proc_lib_file, Some(source)); let all_crates = crate_graph.iter().collect::>(); @@ -396,7 +412,7 @@ impl ChangeFixture { change.source_change.set_roots(roots); change.source_change.set_crate_graph(crate_graph); - ChangeFixture { file_position, files, change } + ChangeFixture { file_position, file_lines, files, change, sysroot_files } } } diff --git a/src/tools/rust-analyzer/crates/test-utils/src/fixture.rs b/src/tools/rust-analyzer/crates/test-utils/src/fixture.rs index c024089a016f..559894ee6205 100644 --- a/src/tools/rust-analyzer/crates/test-utils/src/fixture.rs +++ b/src/tools/rust-analyzer/crates/test-utils/src/fixture.rs @@ -132,13 +132,17 @@ pub struct Fixture { pub library: bool, /// Actual file contents. All meta comments are stripped. pub text: String, + /// The line number in the original fixture of the beginning of this fixture. + pub line: usize, } +#[derive(Debug)] pub struct MiniCore { activated_flags: Vec, valid_flags: Vec, } +#[derive(Debug)] pub struct FixtureWithProjectMeta { pub fixture: Vec, pub mini_core: Option, @@ -184,40 +188,49 @@ impl FixtureWithProjectMeta { let mut mini_core = None; let mut res: Vec = Vec::new(); let mut proc_macro_names = vec![]; + let mut first_row = 0; if let Some(meta) = fixture.strip_prefix("//- toolchain:") { + first_row += 1; let (meta, remain) = meta.split_once('\n').unwrap(); toolchain = Some(meta.trim().to_owned()); fixture = remain; } if let Some(meta) = fixture.strip_prefix("//- target_data_layout:") { + first_row += 1; let (meta, remain) = meta.split_once('\n').unwrap(); meta.trim().clone_into(&mut target_data_layout); fixture = remain; } if let Some(meta) = fixture.strip_prefix("//- target_arch:") { + first_row += 1; let (meta, remain) = meta.split_once('\n').unwrap(); meta.trim().clone_into(&mut target_arch); fixture = remain; } if let Some(meta) = fixture.strip_prefix("//- proc_macros:") { + first_row += 1; let (meta, remain) = meta.split_once('\n').unwrap(); proc_macro_names = meta.split(',').map(|it| it.trim().to_owned()).collect(); fixture = remain; } if let Some(meta) = fixture.strip_prefix("//- minicore:") { + first_row += 1; 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((first_row - 1, "//- /main.rs")) }; - for (ix, line) in default.into_iter().chain(fixture.split_inclusive('\n')).enumerate() { + for (ix, line) in + default.into_iter().chain((first_row..).zip(fixture.split_inclusive('\n'))) + { if line.contains("//-") { assert!( line.starts_with("//-"), @@ -228,7 +241,7 @@ impl FixtureWithProjectMeta { } if let Some(line) = line.strip_prefix("//-") { - let meta = Self::parse_meta_line(line); + let meta = Self::parse_meta_line(line, (ix + 1).try_into().unwrap()); res.push(meta); } else { if matches!(line.strip_prefix("// "), Some(l) if l.trim().starts_with('/')) { @@ -252,7 +265,7 @@ impl FixtureWithProjectMeta { } //- /lib.rs crate:foo deps:bar,baz cfg:foo=a,bar=b env:OUTDIR=path/to,OTHER=foo - fn parse_meta_line(meta: &str) -> Fixture { + fn parse_meta_line(meta: &str, line: usize) -> Fixture { let meta = meta.trim(); let mut components = meta.split_ascii_whitespace(); @@ -317,6 +330,7 @@ impl FixtureWithProjectMeta { Fixture { path, text: String::new(), + line, krate, deps, extern_prelude, @@ -330,7 +344,7 @@ impl FixtureWithProjectMeta { } impl MiniCore { - const RAW_SOURCE: &'static str = include_str!("./minicore.rs"); + pub const RAW_SOURCE: &'static str = include_str!("./minicore.rs"); fn has_flag(&self, flag: &str) -> bool { self.activated_flags.iter().any(|it| it == flag) @@ -363,8 +377,8 @@ impl MiniCore { res } - pub fn available_flags() -> impl Iterator { - let lines = MiniCore::RAW_SOURCE.split_inclusive('\n'); + pub fn available_flags(raw_source: &str) -> impl Iterator { + let lines = raw_source.split_inclusive('\n'); lines .map_while(|x| x.strip_prefix("//!")) .skip_while(|line| !line.contains("Available flags:")) @@ -375,9 +389,9 @@ impl MiniCore { /// Strips parts of minicore.rs which are flagged by inactive flags. /// /// This is probably over-engineered to support flags dependencies. - pub fn source_code(mut self) -> String { + pub fn source_code(mut self, raw_source: &str) -> String { let mut buf = String::new(); - let mut lines = MiniCore::RAW_SOURCE.split_inclusive('\n'); + let mut lines = raw_source.split_inclusive('\n'); let mut implications = Vec::new();