Improve fixture support

Support more features beside highlighting, and support items from minicore.
This commit is contained in:
Chayim Refael Friedman 2025-10-16 19:34:14 +03:00
parent 082ecd8f73
commit db6734e22f
69 changed files with 1652 additions and 512 deletions

View file

@ -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",

View file

@ -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"

View file

@ -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<Item = CompletionItem>) {
self.buf.extend(items)
}
fn add_opt(&mut self, item: Option<CompletionItem>) {
if let Some(item) = item {
self.buf.push(item)

View file

@ -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
"#]],
);
}
}

View file

@ -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)]

View file

@ -440,6 +440,7 @@ pub(crate) struct CompletionContext<'a> {
pub(crate) config: &'a CompletionConfig<'a>,
pub(crate) position: FilePosition,
pub(crate) trigger_character: Option<char>,
/// 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<char>,
) -> 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,

View file

@ -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

View file

@ -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.

View file

@ -187,7 +187,7 @@ pub fn completions(
position: FilePosition,
trigger_character: Option<char>,
) -> Option<Vec<CompletionItem>> {
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,

View file

@ -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 {

View file

@ -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, || {

View file

@ -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

View file

@ -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()
}
}

View file

@ -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<FileId>), ()> {
// 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::<String>() {
s.as_str()
} else {
"Box<dyn Any>"
},
text,
);
})
}
}
pub struct RaFixtureAnalysis {
pub db: RootDatabase,
tmp_file_ids: Vec<(FileId, usize)>,
line_offsets: Vec<TextSize>,
virtual_file_id_to_line: Vec<usize>,
mapper: RangeMapper,
literal: ast::String,
// `minicore` etc..
sysroot_files: Vec<FileId>,
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<RaFixtureAnalysis> {
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<Item = FileId> {
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<usize> {
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<Item = TextRange> {
// 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<TextSize> {
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<Self, ()>;
}
trait IsEmpty {
fn is_empty(&self) -> bool;
}
impl<T> IsEmpty for Vec<T> {
fn is_empty(&self) -> bool {
self.is_empty()
}
}
impl<T, const N: usize> IsEmpty for SmallVec<[T; N]> {
fn is_empty(&self) -> bool {
self.is_empty()
}
}
#[allow(clippy::disallowed_types)]
impl<K, V, S> IsEmpty for std::collections::HashMap<K, V, S> {
fn is_empty(&self) -> bool {
self.is_empty()
}
}
fn upmap_collection<T, Collection>(
collection: Collection,
analysis: &RaFixtureAnalysis,
virtual_file_id: FileId,
real_file_id: FileId,
) -> Result<Collection, ()>
where
T: UpmapFromRaFixture,
Collection: IntoIterator<Item = T> + FromIterator<T> + 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::<Collection>();
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<T: UpmapFromRaFixture> UpmapFromRaFixture for Option<T> {
fn upmap_from_ra_fixture(
self,
analysis: &RaFixtureAnalysis,
virtual_file_id: FileId,
real_file_id: FileId,
) -> Result<Self, ()> {
Ok(match self {
Some(it) => Some(it.upmap_from_ra_fixture(analysis, virtual_file_id, real_file_id)?),
None => None,
})
}
}
impl<T: UpmapFromRaFixture> UpmapFromRaFixture for Vec<T> {
fn upmap_from_ra_fixture(
self,
analysis: &RaFixtureAnalysis,
virtual_file_id: FileId,
real_file_id: FileId,
) -> Result<Self, ()> {
upmap_collection(self, analysis, virtual_file_id, real_file_id)
}
}
impl<T: UpmapFromRaFixture, const N: usize> UpmapFromRaFixture for SmallVec<[T; N]> {
fn upmap_from_ra_fixture(
self,
analysis: &RaFixtureAnalysis,
virtual_file_id: FileId,
real_file_id: FileId,
) -> Result<Self, ()> {
upmap_collection(self, analysis, virtual_file_id, real_file_id)
}
}
#[allow(clippy::disallowed_types)]
impl<K: UpmapFromRaFixture + Hash + Eq, V: UpmapFromRaFixture, S: BuildHasher + Default>
UpmapFromRaFixture for std::collections::HashMap<K, V, S>
{
fn upmap_from_ra_fixture(
self,
analysis: &RaFixtureAnalysis,
virtual_file_id: FileId,
real_file_id: FileId,
) -> Result<Self, ()> {
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<V: UpmapFromRaFixture, S: BuildHasher + Default> UpmapFromRaFixture
for std::collections::HashMap<FileId, V, S>
{
fn upmap_from_ra_fixture(
self,
analysis: &RaFixtureAnalysis,
_virtual_file_id: FileId,
real_file_id: FileId,
) -> Result<Self, ()> {
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::<std::collections::HashMap<_, _, _>>();
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<Self, ()> {
#[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<Self, ()> {
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<Self, ()> {
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<Self, ()> {
// Ok(real_file_id)
// }
// }
impl UpmapFromRaFixture for FilePositionWrapper<FileId> {
fn upmap_from_ra_fixture(
self,
analysis: &RaFixtureAnalysis,
_virtual_file_id: FileId,
real_file_id: FileId,
) -> Result<Self, ()> {
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<FileId> {
fn upmap_from_ra_fixture(
self,
analysis: &RaFixtureAnalysis,
_virtual_file_id: FileId,
real_file_id: FileId,
) -> Result<Self, ()> {
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<Self, ()> {
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,
);

View file

@ -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<TextRange>)>,
}
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<TextSize>) {
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<Item = TextRange> + '_ {
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<TextSize> {
// 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())
}
}

View file

@ -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 {

View file

@ -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<Indel>,

View file

@ -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)))
}

View file

@ -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

View file

@ -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<Vec<FileRange>> },
}
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<Annotation> {
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);

View file

@ -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<FileRange>,
}
#[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<RangeInfo<Vec<NavigationTarget>>> {
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<Vec<CallItem>> {
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<Vec<CallItem>> {
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"));
}

View file

@ -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<RangeInfo<Vec<NavigationTarget>>> {
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;

View file

@ -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<RangeInfo<Vec<NavigationTarget>>> {
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::<Vec<NavigationTarget>>();
.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();
}
"#)
}
"##,
);
}
}

View file

@ -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<MemoryLayoutHoverConfig>,
pub documentation: bool,
@ -44,6 +47,7 @@ pub struct HoverConfig {
pub max_enum_variants_count: Option<usize>,
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<HoverAction>,
@ -130,7 +134,7 @@ pub struct HoverResult {
pub(crate) fn hover(
db: &RootDatabase,
frange @ FileRange { file_id, range }: FileRange,
config: &HoverConfig,
config: &HoverConfig<'_>,
) -> Option<RangeInfo<HoverResult>> {
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<RangeInfo<HoverResult>> {
@ -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<RangeInfo<HoverResult>> {
@ -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<u32>,
render_extras: bool,
config: &HoverConfig,
config: &HoverConfig<'_>,
edition: Edition,
display_target: DisplayTarget,
) -> HoverResult {

View file

@ -35,7 +35,7 @@ use crate::{
pub(super) fn type_info_of(
sema: &Semantics<'_, RootDatabase>,
_config: &HoverConfig,
_config: &HoverConfig<'_>,
expr_or_pat: &Either<ast::Expr, ast::Pat>,
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<DocsRangeMap>,
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<u32>,
render_extras: bool,
subst_types: Option<&Vec<(Symbol, Type<'_>)>>,
config: &HoverConfig,
config: &HoverConfig<'_>,
edition: Edition,
display_target: DisplayTarget,
) -> (Markup, Option<DocsRangeMap>) {
@ -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,

View file

@ -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) {

View file

@ -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<TextRange>,
config: &InlayHintsConfig,
config: &InlayHintsConfig<'_>,
) -> Vec<InlayHint> {
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<InlayHint> {
let _p = tracing::info_span!("inlay_hints_resolve").entered();
@ -208,7 +212,7 @@ fn hints(
hints: &mut Vec<InlayHint>,
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<usize>,
pub closing_brace_hints_min_lines: Option<usize>,
pub fields_to_resolve: InlayFieldsToResolve,
pub minicore: MiniCore<'a>,
}
impl InlayHintsConfig {
impl InlayHintsConfig<'_> {
fn lazy_text_edit(&self, finish: impl FnOnce() -> TextEdit) -> LazyProperty<TextEdit> {
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<T> {
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<InlayHintLabel> {
@ -734,7 +742,7 @@ fn label_of_ty(
mut max_length: Option<usize>,
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);

View file

@ -23,7 +23,7 @@ use crate::{
pub(super) fn hints(
acc: &mut Vec<InlayHint>,
FamousDefs(sema, _): &FamousDefs<'_, '_>,
config: &InlayHintsConfig,
config: &InlayHintsConfig<'_>,
display_target: DisplayTarget,
expr: &ast::Expr,
) -> Option<()> {

View file

@ -20,7 +20,7 @@ use crate::{
pub(super) fn hints(
acc: &mut Vec<InlayHint>,
famous_defs @ FamousDefs(sema, _): &FamousDefs<'_, '_>,
config: &InlayHintsConfig,
config: &InlayHintsConfig<'_>,
display_target: DisplayTarget,
pat: &ast::IdentPat,
) -> Option<()> {

View file

@ -15,7 +15,7 @@ use crate::{InlayHint, InlayHintLabel, InlayHintPosition, InlayHintsConfig, Inla
pub(super) fn hints(
acc: &mut Vec<InlayHint>,
FamousDefs(sema, _): &FamousDefs<'_, '_>,
config: &InlayHintsConfig,
config: &InlayHintsConfig<'_>,
pat: &ast::Pat,
) -> Option<()> {
if !config.binding_mode_hints {

View file

@ -13,7 +13,7 @@ use crate::{
pub(super) fn hints(
acc: &mut Vec<InlayHint>,
famous_defs @ FamousDefs(sema, _): &FamousDefs<'_, '_>,
config: &InlayHintsConfig,
config: &InlayHintsConfig<'_>,
params: ast::GenericParamList,
) -> Option<()> {
if !config.sized_bound {

View file

@ -13,7 +13,7 @@ use super::label_of_ty;
pub(super) fn hints(
acc: &mut Vec<InlayHint>,
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,
) {

View file

@ -19,7 +19,7 @@ use crate::{
pub(super) fn hints(
acc: &mut Vec<InlayHint>,
sema: &Semantics<'_, RootDatabase>,
config: &InlayHintsConfig,
config: &InlayHintsConfig<'_>,
display_target: DisplayTarget,
InRealFile { file_id, value: node }: InRealFile<SyntaxNode>,
) -> Option<()> {

View file

@ -13,7 +13,7 @@ use crate::{
pub(super) fn hints(
acc: &mut Vec<InlayHint>,
FamousDefs(sema, _): &FamousDefs<'_, '_>,
config: &InlayHintsConfig,
config: &InlayHintsConfig<'_>,
closure: ast::ClosureExpr,
) -> Option<()> {
if !config.closure_capture_hints {

View file

@ -13,7 +13,7 @@ use crate::{
pub(super) fn hints(
acc: &mut Vec<InlayHint>,
famous_defs @ FamousDefs(sema, _): &FamousDefs<'_, '_>,
config: &InlayHintsConfig,
config: &InlayHintsConfig<'_>,
display_target: DisplayTarget,
closure: ast::ClosureExpr,
) -> Option<()> {

View file

@ -17,7 +17,7 @@ use crate::{
pub(super) fn enum_hints(
acc: &mut Vec<InlayHint>,
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<InlayHint>,
config: &InlayHintsConfig,
config: &InlayHintsConfig<'_>,
sema: &Semantics<'_, RootDatabase>,
enum_: &ast::Enum,
variant: &ast::Variant,

View file

@ -7,7 +7,7 @@ use crate::{InlayHint, InlayHintsConfig};
pub(super) fn extern_block_hints(
acc: &mut Vec<InlayHint>,
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<InlayHint>,
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<InlayHint>,
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 {

View file

@ -16,7 +16,7 @@ use super::param_name::is_argument_similar_to_param_name;
pub(crate) fn hints(
acc: &mut Vec<InlayHint>,
FamousDefs(sema, krate): &FamousDefs<'_, '_>,
config: &InlayHintsConfig,
config: &InlayHintsConfig<'_>,
node: AnyHasGenericArgs,
) -> Option<()> {
let GenericParameterHints { type_hints, lifetime_hints, const_hints } =

View file

@ -23,7 +23,7 @@ use crate::{InlayHint, InlayHintLabel, InlayHintPosition, InlayHintsConfig, Inla
pub(super) fn hints(
acc: &mut Vec<InlayHint>,
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]

View file

@ -15,7 +15,7 @@ use crate::{InlayHint, InlayHintPosition, InlayHintsConfig, InlayKind, LifetimeE
pub(super) fn hints(
acc: &mut Vec<InlayHint>,
FamousDefs(_sema, _): &FamousDefs<'_, '_>,
config: &InlayHintsConfig,
config: &InlayHintsConfig<'_>,
statik_or_const: Either<ast::Static, ast::Const>,
) -> Option<()> {
if config.lifetime_elision_hints != LifetimeElisionHints::Always {

View file

@ -11,7 +11,7 @@ use crate::{InlayHint, InlayHintLabel, InlayHintPosition, InlayHintsConfig, Inla
pub(super) fn hints(
acc: &mut Vec<InlayHint>,
FamousDefs(sema, _): &FamousDefs<'_, '_>,
config: &InlayHintsConfig,
config: &InlayHintsConfig<'_>,
path: Either<ast::PathType, ast::DynTraitType>,
) -> Option<()> {
let parent = path.syntax().parent()?;

View file

@ -21,7 +21,7 @@ pub(super) fn fn_hints(
acc: &mut Vec<InlayHint>,
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<InlayHint>,
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<InlayHint>,
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<InlayHint>,
ctx: &mut InlayHintCtx,
FamousDefs(_, _): &FamousDefs<'_, '_>,
config: &InlayHintsConfig,
config: &InlayHintsConfig<'_>,
params: impl Iterator<Item = (Option<ast::Name>, ast::Type)>,
generic_param_list: Option<ast::GenericParamList>,
ret_type: Option<ast::RetType>,

View file

@ -18,7 +18,7 @@ use crate::{InlayHint, InlayHintLabel, InlayHintPosition, InlayHintsConfig, Inla
pub(super) fn hints(
acc: &mut Vec<InlayHint>,
FamousDefs(sema, krate): &FamousDefs<'_, '_>,
config: &InlayHintsConfig,
config: &InlayHintsConfig<'_>,
file_id: EditionedFileId,
expr: ast::Expr,
) -> Option<()> {

View file

@ -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<InlayHint>,
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);

View file

@ -11,7 +11,7 @@ use crate::{InlayHint, InlayHintsConfig};
pub(super) fn hints(
acc: &mut Vec<InlayHint>,
FamousDefs(_sema, _): &FamousDefs<'_, '_>,
config: &InlayHintsConfig,
config: &InlayHintsConfig<'_>,
range: impl ast::RangeItem,
) -> Option<()> {
(config.range_exclusive_hints && range.end().is_some())

View file

@ -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<T> = Result<T, Cancelled>;
/// Info associated with a text range.
#[derive(Debug)]
#[derive(Debug, UpmapFromRaFixture)]
pub struct RangeInfo<T> {
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<FileId>) -> Cancellable<String> {
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<TextRange>,
) -> Cancellable<Vec<InlayHint>> {
@ -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<Option<RangeInfo<Vec<NavigationTarget>>>> {
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<Option<RangeInfo<Vec<NavigationTarget>>>> {
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<SearchScope>,
config: &FindAllRefsConfig<'_>,
) -> Cancellable<Option<Vec<ReferenceSearchResult>>> {
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<Option<RangeInfo<HoverResult>>> {
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<Option<RangeInfo<Vec<NavigationTarget>>>> {
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<Option<Vec<CallItem>>> {
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<Option<Vec<CallItem>>> {
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<Vec<HlRange>> {
// 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<Vec<HlRange>> {
// 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<String> {
// 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<String> {
// 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<Vec<Annotation>> {
self.with_db(|db| annotations::annotations(db, config, file_id))
}
pub fn resolve_annotation(&self, annotation: Annotation) -> Cancellable<Annotation> {
self.with_db(|db| annotations::resolve_annotation(db, annotation))
pub fn resolve_annotation(
&self,
config: &AnnotationConfig<'_>,
annotation: Annotation,
) -> Cancellable<Annotation> {
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<HirDatabase>` 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)))
}
}

View file

@ -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);

View file

@ -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<Self, ()> {
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<NavigationTarget>;
}

View file

@ -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<SearchScope>,
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<SearchScope>,
config: &FindAllRefsConfig<'_>,
) -> Option<Vec<ReferenceSearchResult>> {
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 {

View file

@ -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),

View file

@ -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!(

View file

@ -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<u64>,
}
#[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<TextRange>,
) -> Vec<HlRange> {
@ -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<hir::Crate>,
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<hir::Crate>,
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,

View file

@ -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,

View file

@ -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,

View file

@ -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<Delta<TextSize>>)>,
}
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<TextSize>) {
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<Item = TextRange> + '_ {
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<T> {
Add(T),
Sub(T),
}
impl<T> Delta<T> {
fn new(from: T, to: T) -> Delta<T>
where
T: Ord + Sub<Output = T>,
{
if to >= from { Delta::Add(to - from) } else { Delta::Sub(from - to) }
}
}
impl ops::Add<Delta<TextSize>> for TextSize {
type Output = TextSize;
fn add(self, rhs: Delta<TextSize>) -> TextSize {
match rhs {
Delta::Add(it) => self + it,
Delta::Sub(it) => self - it,
}
}
}
impl ops::Add<Delta<TextSize>> for TextRange {
type Output = TextRange;
fn add(self, rhs: Delta<TextSize>) -> TextRange {
TextRange::at(self.start() + rhs, self.len())
}
}

View file

@ -43,18 +43,19 @@ pre { color: #DCDCCC; background: #3F3F3F; font-size: 22px; padd
<pre><code><span class="keyword">fn</span> <span class="function declaration">fixture</span><span class="parenthesis">(</span><span class="attribute_bracket attribute">#</span><span class="attribute_bracket attribute">[</span><span class="tool_module attribute">rust_analyzer</span><span class="operator attribute">::</span><span class="tool_module attribute">rust_fixture</span><span class="attribute_bracket attribute">]</span> <span class="value_param declaration reference">ra_fixture</span><span class="colon">:</span> <span class="punctuation">&</span><span class="builtin_type">str</span><span class="parenthesis">)</span> <span class="brace">{</span><span class="brace">}</span>
<span class="keyword">fn</span> <span class="function declaration">main</span><span class="parenthesis">(</span><span class="parenthesis">)</span> <span class="brace">{</span>
<span class="function">fixture</span><span class="parenthesis">(</span><span class="string_literal">r#"</span><span class="none injected">
</span><span class="keyword injected">trait</span><span class="none injected"> </span><span class="trait declaration injected">Foo</span><span class="none injected"> </span><span class="brace injected">{</span><span class="none injected">
</span><span class="keyword injected">fn</span><span class="none injected"> </span><span class="function associated declaration injected static trait">foo</span><span class="parenthesis injected">(</span><span class="parenthesis injected">)</span><span class="none injected"> </span><span class="brace injected">{</span><span class="none injected">
</span><span class="unresolved_reference injected">println</span><span class="macro_bang injected">!</span><span class="parenthesis injected">(</span><span class="string_literal injected">"2 + 2 = {}"</span><span class="comma injected">,</span><span class="none injected"> </span><span class="numeric_literal injected">4</span><span class="parenthesis injected">)</span><span class="semicolon injected">;</span><span class="none injected">
</span><span class="brace injected">}</span><span class="none injected">
<span class="function">fixture</span><span class="parenthesis">(</span><span class="string_literal">r#"</span>
@@- minicore: sized
<span class="keyword injected">trait</span><span class="none injected"> </span><span class="trait declaration injected">Foo</span><span class="colon injected">:</span><span class="none injected"> </span><span class="trait default_library injected library">Sized</span><span class="none injected"> </span><span class="brace injected">{</span><span class="none injected">
</span> <span class="keyword injected">fn</span><span class="none injected"> </span><span class="function associated declaration injected static trait">foo</span><span class="parenthesis injected">(</span><span class="parenthesis injected">)</span><span class="none injected"> </span><span class="brace injected">{</span><span class="none injected">
</span> <span class="unresolved_reference injected">println</span><span class="macro_bang injected">!</span><span class="parenthesis injected">(</span><span class="string_literal injected">"2 + 2 = {}"</span><span class="comma injected">,</span><span class="none injected"> </span><span class="numeric_literal injected">4</span><span class="parenthesis injected">)</span><span class="semicolon injected">;</span><span class="none injected">
</span> <span class="brace injected">}</span><span class="none injected">
</span><span class="brace injected">}</span><span class="string_literal">"#</span>
<span class="parenthesis">)</span><span class="semicolon">;</span>
<span class="function">fixture</span><span class="parenthesis">(</span><span class="string_literal">r"</span><span class="none injected">
</span><span class="keyword injected">fn</span><span class="none injected"> </span><span class="function declaration injected">foo</span><span class="parenthesis injected">(</span><span class="parenthesis injected">)</span><span class="none injected"> </span><span class="brace injected">{</span><span class="none injected">
</span><span class="function injected">foo</span><span class="parenthesis injected">(</span><span class="keyword injected">$0</span><span class="brace injected">{</span><span class="none injected">
</span><span class="numeric_literal injected">92</span><span class="none injected">
</span><span class="brace injected">}</span><span class="keyword injected">$0</span><span class="parenthesis injected">)</span><span class="none injected">
<span class="function">fixture</span><span class="parenthesis">(</span><span class="string_literal">r"</span>
<span class="keyword injected">fn</span><span class="none injected"> </span><span class="function declaration injected">foo</span><span class="parenthesis injected">(</span><span class="parenthesis injected">)</span><span class="none injected"> </span><span class="brace injected">{</span><span class="none injected">
</span> <span class="function injected">foo</span><span class="parenthesis injected">(</span><span class="keyword injected">$0</span><span class="brace injected">{</span><span class="none injected">
</span> <span class="numeric_literal injected">92</span><span class="none injected">
</span> <span class="brace injected">}</span><span class="keyword injected">$0</span><span class="parenthesis injected">)</span><span class="none injected">
</span><span class="brace injected">}</span><span class="string_literal">"</span>
<span class="parenthesis">)</span><span class="semicolon">;</span>
<span class="brace">}</span></code></pre>

View file

@ -0,0 +1,61 @@
<style>
body { margin: 0; }
pre { color: #DCDCCC; background: #3F3F3F; font-size: 22px; padding: 0.4em; }
.lifetime { color: #DFAF8F; font-style: italic; }
.label { color: #DFAF8F; font-style: italic; }
.comment { color: #7F9F7F; }
.documentation { color: #629755; }
.intra_doc_link { font-style: italic; }
.injected { opacity: 0.65 ; }
.struct, .enum { color: #7CB8BB; }
.enum_variant { color: #BDE0F3; }
.string_literal { color: #CC9393; }
.field { color: #94BFF3; }
.function { color: #93E0E3; }
.parameter { color: #94BFF3; }
.text { color: #DCDCCC; }
.type { color: #7CB8BB; }
.builtin_type { color: #8CD0D3; }
.type_param { color: #DFAF8F; }
.attribute { color: #94BFF3; }
.numeric_literal { color: #BFEBBF; }
.bool_literal { color: #BFE6EB; }
.macro { color: #94BFF3; }
.proc_macro { color: #94BFF3; text-decoration: underline; }
.derive { color: #94BFF3; font-style: italic; }
.module { color: #AFD8AF; }
.value_param { color: #DCDCCC; }
.variable { color: #DCDCCC; }
.format_specifier { color: #CC696B; }
.mutable { text-decoration: underline; }
.escape_sequence { color: #94BFF3; }
.keyword { color: #F0DFAF; font-weight: bold; }
.control { font-style: italic; }
.reference { font-style: italic; font-weight: bold; }
.const { font-weight: bolder; }
.unsafe { color: #BC8383; }
.invalid_escape_sequence { color: #FC5555; text-decoration: wavy underline; }
.unresolved_reference { color: #FC5555; text-decoration: wavy underline; }
</style>
<pre><code><span class="keyword">fn</span> <span class="function declaration">fixture</span><span class="parenthesis">(</span><span class="attribute_bracket attribute">#</span><span class="attribute_bracket attribute">[</span><span class="tool_module attribute">rust_analyzer</span><span class="operator attribute">::</span><span class="tool_module attribute">rust_fixture</span><span class="attribute_bracket attribute">]</span> <span class="value_param declaration reference">ra_fixture</span><span class="colon">:</span> <span class="punctuation">&</span><span class="builtin_type">str</span><span class="parenthesis">)</span> <span class="brace">{</span><span class="brace">}</span>
<span class="keyword">fn</span> <span class="function declaration">main</span><span class="parenthesis">(</span><span class="parenthesis">)</span> <span class="brace">{</span>
<span class="function">fixture</span><span class="parenthesis">(</span><span class="string_literal">r#"</span>
@@- /main.rs crate:main deps:other_crate
<span class="keyword injected">fn</span><span class="none injected"> </span><span class="function declaration injected">test</span><span class="parenthesis injected">(</span><span class="parenthesis injected">)</span><span class="none injected"> </span><span class="brace injected">{</span><span class="none injected">
</span> <span class="keyword injected">let</span><span class="none injected"> </span><span class="variable declaration injected">x</span><span class="none injected"> </span><span class="operator injected">=</span><span class="none injected"> </span><span class="module crate_root injected library">other_crate</span><span class="operator injected">::</span><span class="module injected library">foo</span><span class="operator injected">::</span><span class="struct injected library">S</span><span class="operator injected">::</span><span class="function associated injected library static">thing</span><span class="parenthesis injected">(</span><span class="parenthesis injected">)</span><span class="semicolon injected">;</span><span class="none injected">
</span> <span class="variable injected">x</span><span class="semicolon injected">;</span><span class="none injected">
</span><span class="brace injected">}</span><span class="none injected"> </span><span class="comment injected">//^ i128</span><span class="none injected">
</span><span class="none injected">
</span>@@- /lib.rs crate:other_crate
<span class="keyword injected">pub</span><span class="none injected"> </span><span class="keyword injected">mod</span><span class="none injected"> </span><span class="module declaration injected public">foo</span><span class="none injected"> </span><span class="brace injected">{</span><span class="none injected">
</span> <span class="keyword injected">pub</span><span class="none injected"> </span><span class="keyword injected">struct</span><span class="none injected"> </span><span class="struct declaration injected public">S</span><span class="semicolon injected">;</span><span class="none injected">
</span> <span class="keyword injected">impl</span><span class="none injected"> </span><span class="struct injected public">S</span><span class="none injected"> </span><span class="brace injected">{</span><span class="none injected">
</span> <span class="keyword injected">pub</span><span class="none injected"> </span><span class="keyword injected">fn</span><span class="none injected"> </span><span class="function associated declaration injected public static">thing</span><span class="parenthesis injected">(</span><span class="parenthesis injected">)</span><span class="none injected"> </span><span class="operator injected">-&gt;</span><span class="none injected"> </span><span class="builtin_type injected">i128</span><span class="none injected"> </span><span class="brace injected">{</span><span class="none injected"> </span><span class="numeric_literal injected">0</span><span class="none injected"> </span><span class="brace injected">}</span><span class="none injected">
</span> <span class="brace injected">}</span><span class="none injected">
</span><span class="brace injected">}</span><span class="none injected">
</span> <span class="none injected"> </span><span class="string_literal">"#</span><span class="parenthesis">)</span><span class="semicolon">;</span>
<span class="brace">}</span></code></pre>

View file

@ -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,
) {

View file

@ -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<Self, ()> {
Ok(match self { #body })
}
},
)
}

View file

@ -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);
}

View file

@ -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<SourceRootId>) -> CompletionConfig<'_> {
pub fn completion<'a>(
&'a self,
source_root: Option<SourceRootId>,
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,
}
}

View file

@ -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<String>,
}
/// 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 {

View file

@ -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, &params.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<lsp_ext::CommandLinkGroup> {
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()?;

View file

@ -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() };

View file

@ -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 {

View file

@ -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

View file

@ -201,6 +201,10 @@ pub trait IsString: AstToken {
None
}
}
fn map_offset_down(&self, offset: TextSize) -> Option<TextSize> {
let contents_range = self.text_range_between_quotes()?;
offset.checked_sub(contents_range.start())
}
}
impl IsString for ast::String {

View file

@ -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<DB: ExpandDatabase + SourceDatabase + Default + 'static> WithFixture for DB
pub struct ChangeFixture {
pub file_position: Option<(EditionedFileId, RangeOrOffset)>,
pub file_lines: Vec<usize>,
pub files: Vec<EditionedFileId>,
pub change: ChangeWithProcMacros,
pub sysroot_files: Vec<FileId>,
}
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::<Vec<_>>();
@ -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 }
}
}

View file

@ -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<String>,
valid_flags: Vec<String>,
}
#[derive(Debug)]
pub struct FixtureWithProjectMeta {
pub fixture: Vec<Fixture>,
pub mini_core: Option<MiniCore>,
@ -184,40 +188,49 @@ impl FixtureWithProjectMeta {
let mut mini_core = None;
let mut res: Vec<Fixture> = 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<Item = &'static str> {
let lines = MiniCore::RAW_SOURCE.split_inclusive('\n');
pub fn available_flags(raw_source: &str) -> impl Iterator<Item = &str> {
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();