diff --git a/crates/ide-diagnostics/src/tests.rs b/crates/ide-diagnostics/src/tests.rs index 1839ab1c58c1..204306f0e808 100644 --- a/crates/ide-diagnostics/src/tests.rs +++ b/crates/ide-diagnostics/src/tests.rs @@ -311,7 +311,7 @@ fn minicore_smoke_test() { } fn check(minicore: MiniCore) { - let source = minicore.source_code(); + let source = minicore.source_code(MiniCore::RAW_SOURCE); let mut config = DiagnosticsConfig::test_sample(); // This should be ignored since we conditionally remove code which creates single item use with braces config.disabled.insert("unused_braces".to_owned()); @@ -321,7 +321,7 @@ fn minicore_smoke_test() { } // Checks that there is no diagnostic in minicore for each flag. - for flag in MiniCore::available_flags() { + for flag in MiniCore::available_flags(MiniCore::RAW_SOURCE) { if flag == "clone" { // Clone without copy has `moved-out-of-ref`, so ignoring. // FIXME: Maybe we should merge copy and clone in a single flag? @@ -332,5 +332,5 @@ fn minicore_smoke_test() { } // And one time for all flags, to check codes which are behind multiple flags + prevent name collisions eprintln!("Checking all minicore flags"); - check(MiniCore::from_flags(MiniCore::available_flags())) + check(MiniCore::from_flags(MiniCore::available_flags(MiniCore::RAW_SOURCE))) } diff --git a/crates/ide/Cargo.toml b/crates/ide/Cargo.toml index 06d2776ebe87..e3baae573ecf 100644 --- a/crates/ide/Cargo.toml +++ b/crates/ide/Cargo.toml @@ -43,16 +43,16 @@ span.workspace = true # something from some `hir-xxx` subpackage, reexport the API via `hir`. hir.workspace = true +test-utils.workspace = true +# local deps +test-fixture.workspace = true + [target.'cfg(not(any(target_arch = "wasm32", target_os = "emscripten")))'.dependencies] toolchain.workspace = true [dev-dependencies] expect-test = "1.5.1" -# local deps -test-utils.workspace = true -test-fixture.workspace = true - [features] in-rust-tree = [] diff --git a/crates/ide/src/lib.rs b/crates/ide/src/lib.rs index 50cbef581c7d..de542628ad80 100644 --- a/crates/ide/src/lib.rs +++ b/crates/ide/src/lib.rs @@ -274,6 +274,41 @@ impl Analysis { (host.analysis(), file_id) } + pub fn from_ra_fixture(text: &str, minicore: &str) -> (Analysis, Vec<(FileId, usize)>) { + // 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 host = AnalysisHost::default(); + let fixture = test_fixture::ChangeFixture::parse_with_proc_macros( + &host.db, + text, + minicore, + Vec::new(), + ); + host.apply_change(fixture.change); + let files = fixture + .files + .into_iter() + .zip(fixture.file_lines) + .map(|(file_id, range)| (file_id.file_id(&host.db), range)) + .collect(); + (host.analysis(), files) + }) + .unwrap_or_else(|error| { + tracing::error!( + "cannot crate the crate graph: {}\nCrate graph:\n{}\n", + if let Some(&s) = error.downcast_ref::<&'static str>() { + s + } else if let Some(s) = error.downcast_ref::() { + s.as_str() + } else { + "Box" + }, + text, + ); + (AnalysisHost::default().analysis(), Vec::new()) + }) + } + /// Debug info about the current state of the analysis. pub fn status(&self, file_id: Option) -> Cancellable { self.with_db(|db| status::status(db, file_id)) @@ -675,20 +710,20 @@ impl Analysis { /// Computes syntax highlighting for the given file pub fn highlight( &self, - highlight_config: HighlightConfig, + highlight_config: HighlightConfig<'_>, file_id: FileId, ) -> Cancellable> { // highlighting may construct a new database for "speculative" execution, so we can't currently attach the database // highlighting instead sets up the attach hook where neceesary for the trait solver Cancelled::catch(|| { - syntax_highlighting::highlight(&self.db, highlight_config, file_id, None) + syntax_highlighting::highlight(&self.db, &highlight_config, file_id, None) }) } /// Computes syntax highlighting for the given file range. pub fn highlight_range( &self, - highlight_config: HighlightConfig, + highlight_config: HighlightConfig<'_>, frange: FileRange, ) -> Cancellable> { // highlighting may construct a new database for "speculative" execution, so we can't currently attach the database @@ -696,7 +731,7 @@ impl Analysis { Cancelled::catch(|| { syntax_highlighting::highlight( &self.db, - highlight_config, + &highlight_config, frange.file_id, Some(frange.range), ) diff --git a/crates/ide/src/syntax_highlighting.rs b/crates/ide/src/syntax_highlighting.rs index f98770805a45..c500695d8cf2 100644 --- a/crates/ide/src/syntax_highlighting.rs +++ b/crates/ide/src/syntax_highlighting.rs @@ -44,7 +44,7 @@ pub struct HlRange { } #[derive(Copy, Clone, Debug, PartialEq, Eq)] -pub struct HighlightConfig { +pub struct HighlightConfig<'a> { /// Whether to highlight strings pub strings: bool, /// Whether to highlight punctuation @@ -61,6 +61,12 @@ pub struct HighlightConfig { pub macro_bang: bool, /// Whether to highlight unresolved things be their syntax pub syntactic_name_ref_highlighting: bool, + /// This is a bit of a special field, it's not really a config, rather it's a way to pass information to highlight. + /// + /// Fixture highlighting requires the presence of a `minicore.rs` file. If such file is believed to be found in + /// the workspace (based on the path), this field stores its contents. Otherwise, highlighting will use the `minicore` + /// that was baked into the rust-analyzer binary. + pub minicore: Option<&'a str>, } // Feature: Semantic Syntax Highlighting @@ -188,7 +194,7 @@ pub struct HighlightConfig { // ![Semantic Syntax Highlighting](https://user-images.githubusercontent.com/48062697/113187625-f7f50100-9250-11eb-825e-91c58f236071.png) pub(crate) fn highlight( db: &RootDatabase, - config: HighlightConfig, + config: &HighlightConfig<'_>, file_id: FileId, range_to_highlight: Option, ) -> Vec { @@ -223,7 +229,7 @@ pub(crate) fn highlight( fn traverse( hl: &mut Highlights, sema: &Semantics<'_, RootDatabase>, - config: HighlightConfig, + config: &HighlightConfig<'_>, InRealFile { file_id, value: root }: InRealFile<&SyntaxNode>, krate: Option, range_to_highlight: TextRange, @@ -488,7 +494,7 @@ fn traverse( fn string_injections( hl: &mut Highlights, sema: &Semantics<'_, RootDatabase>, - config: HighlightConfig, + config: &HighlightConfig<'_>, file_id: EditionedFileId, krate: Option, token: SyntaxToken, @@ -585,7 +591,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, // If punctuation is disabled, make the macro bang part of the macro call again. diff --git a/crates/ide/src/syntax_highlighting/html.rs b/crates/ide/src/syntax_highlighting/html.rs index 9fd807f031f1..c8a82ee57181 100644 --- a/crates/ide/src/syntax_highlighting/html.rs +++ b/crates/ide/src/syntax_highlighting/html.rs @@ -29,7 +29,7 @@ pub(crate) fn highlight_as_html(db: &RootDatabase, file_id: FileId, rainbow: boo let hl_ranges = highlight( db, - HighlightConfig { + &HighlightConfig { strings: true, punctuation: true, specialize_punctuation: true, @@ -38,6 +38,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: None, }, file_id.file_id(db), None, diff --git a/crates/ide/src/syntax_highlighting/inject.rs b/crates/ide/src/syntax_highlighting/inject.rs index abe7be8c6888..9421a5169061 100644 --- a/crates/ide/src/syntax_highlighting/inject.rs +++ b/crates/ide/src/syntax_highlighting/inject.rs @@ -12,6 +12,7 @@ use syntax::{ AstToken, NodeOrToken, SyntaxNode, TextRange, TextSize, ast::{self, AstNode, IsString, QuoteOffsets}, }; +use test_utils::MiniCore; use crate::{ Analysis, HlMod, HlRange, HlTag, RootDatabase, @@ -22,7 +23,7 @@ use crate::{ pub(super) fn ra_fixture( hl: &mut Highlights, sema: &Semantics<'_, RootDatabase>, - config: HighlightConfig, + config: &HighlightConfig<'_>, literal: &ast::String, expanded: &ast::String, ) -> Option<()> { @@ -44,59 +45,126 @@ pub(super) fn ra_fixture( hl.add(HlRange { range, highlight: HlTag::StringLiteral.into(), binding_hash: None }) } + let minicore = config.minicore.unwrap_or(MiniCore::RAW_SOURCE); + let mut inj = Injector::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; - let mut offset: TextSize = 0.into(); + 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. + inj.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); - 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, - }); + 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()..]; + + inj.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))) + { + hl.add(HlRange { + range: marker_range, + highlight: HlTag::Keyword | HlMod::Injected, + binding_hash: None, + }); + } + offset_with_indent += TextSize::of(marker); + + inj.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 => { + inj.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); } - - text = next; - - let marker_len = TextSize::of(marker); - offset += marker_len; } } - let (analysis, tmp_file_id) = Analysis::from_single_file(inj.take_text()); + let (analysis, tmp_file_ids) = Analysis::from_ra_fixture(&inj.take_text(), minicore); - for mut hl_range in analysis - .highlight( - HighlightConfig { - syntactic_name_ref_highlighting: false, - 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) { - hl_range.range = range; - hl_range.highlight |= HlMod::Injected; - hl.add(hl_range); + for (tmp_file_id, tmp_file_line) in tmp_file_ids { + // This could be `None` if the file is empty. + let Some(&tmp_file_offset) = line_offsets.get(tmp_file_line) else { + continue; + }; + for mut hl_range in analysis + .highlight( + HighlightConfig { + syntactic_name_ref_highlighting: false, + 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() + { + // Resolve the offset relative to the virtual file to an offset relative to the combined indentation-trimmed file + let range = hl_range.range + tmp_file_offset; + // Then resolve that to an offset relative to the real file. + for range in inj.map_range_up(range) { + // And finally resolve the offset relative to the literal to relative to the file. + if let Some(range) = literal.map_range_up(range) { + hl_range.range = range; + hl_range.highlight |= HlMod::Injected; + hl.add(hl_range); + } } } } @@ -115,7 +183,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, ) { @@ -248,7 +316,7 @@ pub(super) fn doc_comment( if let Ok(ranges) = analysis.with_db(|db| { super::highlight( db, - HighlightConfig { + &HighlightConfig { syntactic_name_ref_highlighting: true, punctuation: true, operator: true, @@ -257,6 +325,7 @@ pub(super) fn doc_comment( specialize_operator: config.operator, inject_doc_comment: config.inject_doc_comment, macro_bang: config.macro_bang, + minicore: config.minicore, }, tmp_file_id, None, diff --git a/crates/ide/src/syntax_highlighting/test_data/highlight_injection.html b/crates/ide/src/syntax_highlighting/test_data/highlight_injection.html index 3b468ab6dba6..579c6ceadcb8 100644 --- a/crates/ide/src/syntax_highlighting/test_data/highlight_injection.html +++ b/crates/ide/src/syntax_highlighting/test_data/highlight_injection.html @@ -43,18 +43,19 @@
fn fixture(#[rust_analyzer::rust_fixture] ra_fixture: &str) {}
 
 fn main() {
-    fixture(r#"
-trait Foo {
-    fn foo() {
-        println!("2 + 2 = {}", 4);
-    }
+    fixture(r#"
+@@- minicore: sized
+trait Foo: Sized {
+    fn foo() {
+        println!("2 + 2 = {}", 4);
+    }
 }"#
     );
-    fixture(r"
-fn foo() {
-    foo($0{
-        92
-    }$0)
+    fixture(r"
+fn foo() {
+    foo($0{
+        92
+    }$0)
 }"
     );
 }
\ No newline at end of file diff --git a/crates/ide/src/syntax_highlighting/test_data/highlight_injection_2.html b/crates/ide/src/syntax_highlighting/test_data/highlight_injection_2.html new file mode 100644 index 000000000000..fc2d9a387016 --- /dev/null +++ b/crates/ide/src/syntax_highlighting/test_data/highlight_injection_2.html @@ -0,0 +1,61 @@ + + +
fn fixture(#[rust_analyzer::rust_fixture] ra_fixture: &str) {}
+
+fn main() {
+    fixture(r#"
+@@- /main.rs crate:main deps:other_crate
+fn test() {
+    let x = other_crate::foo::S::thing();
+    x;
+} //^ i128
+
+@@- /lib.rs crate:other_crate
+pub mod foo {
+    pub struct S;
+    impl S {
+        pub fn thing() -> i128 { 0 }
+    }
+}
+    "#);
+}
\ No newline at end of file diff --git a/crates/ide/src/syntax_highlighting/tests.rs b/crates/ide/src/syntax_highlighting/tests.rs index dd359326c61d..38cc4f01121b 100644 --- a/crates/ide/src/syntax_highlighting/tests.rs +++ b/crates/ide/src/syntax_highlighting/tests.rs @@ -7,7 +7,7 @@ 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, punctuation: true, specialize_punctuation: true, @@ -16,6 +16,7 @@ const HL_CONFIG: HighlightConfig = HighlightConfig { inject_doc_comment: true, macro_bang: true, syntactic_name_ref_highlighting: false, + minicore: None, }; #[test] @@ -1015,6 +1016,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( @@ -1023,7 +1053,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); } diff --git a/crates/rust-analyzer/src/config.rs b/crates/rust-analyzer/src/config.rs index 84f158cdea69..4c5187a21041 100644 --- a/crates/rust-analyzer/src/config.rs +++ b/crates/rust-analyzer/src/config.rs @@ -1965,7 +1965,7 @@ impl Config { self.semanticHighlighting_nonStandardTokens().to_owned() } - pub fn highlighting_config(&self) -> HighlightConfig { + pub fn highlighting_config<'a>(&self, minicore: Option<&'a str>) -> HighlightConfig<'a> { HighlightConfig { strings: self.semanticHighlighting_strings_enable().to_owned(), punctuation: self.semanticHighlighting_punctuation_enable().to_owned(), @@ -1979,6 +1979,7 @@ impl Config { .to_owned(), inject_doc_comment: self.semanticHighlighting_doc_comment_inject_enable().to_owned(), syntactic_name_ref_highlighting: false, + minicore, } } diff --git a/crates/rust-analyzer/src/global_state.rs b/crates/rust-analyzer/src/global_state.rs index 2f1afba3634e..7d786ab97045 100644 --- a/crates/rust-analyzer/src/global_state.rs +++ b/crates/rust-analyzer/src/global_state.rs @@ -136,6 +136,7 @@ pub(crate) struct GlobalState { // as that handle's lifetime is the same as `GlobalState` itself. pub(crate) vfs_span: Option, pub(crate) wants_to_switch: Option, + pub(crate) minicore_file_id: Option, /// `workspaces` field stores the data we actually use, while the `OpQueue` /// stores the result of the last fetch. @@ -193,6 +194,7 @@ pub(crate) struct GlobalStateSnapshot { mem_docs: MemDocs, pub(crate) semantic_tokens_cache: Arc>>, vfs: Arc)>>, + minicore_file_id: Option, pub(crate) workspaces: Arc>, // used to signal semantic highlighting to fall back to syntax based highlighting until // proc-macros have been loaded @@ -286,6 +288,7 @@ impl GlobalState { vfs_span: None, vfs_done: true, wants_to_switch: None, + minicore_file_id: None, workspaces: Arc::from(Vec::new()), crate_graph_file_dependencies: FxHashSet::default(), @@ -544,6 +547,7 @@ impl GlobalState { workspaces: Arc::clone(&self.workspaces), analysis: self.analysis_host.analysis(), vfs: Arc::clone(&self.vfs), + minicore_file_id: self.minicore_file_id, check_fixes: Arc::clone(&self.diagnostics.check_fixes), mem_docs: self.mem_docs.clone(), semantic_tokens_cache: Arc::clone(&self.semantic_tokens_cache), @@ -810,6 +814,13 @@ impl GlobalStateSnapshot { pub(crate) fn file_exists(&self, file_id: FileId) -> bool { self.vfs.read().0.exists(file_id) } + + pub(crate) fn minicore(&self) -> Cancellable>> { + match self.minicore_file_id { + Some(file_id) => self.analysis.file_text(file_id).map(Some), + None => Ok(None), + } + } } pub(crate) fn file_id_to_url(vfs: &vfs::Vfs, id: FileId) -> Url { diff --git a/crates/rust-analyzer/src/handlers/request.rs b/crates/rust-analyzer/src/handlers/request.rs index 6cb28aecf748..be9c0aa8366b 100644 --- a/crates/rust-analyzer/src/handlers/request.rs +++ b/crates/rust-analyzer/src/handlers/request.rs @@ -1916,7 +1916,8 @@ 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 minicore = snap.minicore()?; + let mut highlight_config = snap.config.highlighting_config(minicore.as_deref()); // 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 +1947,8 @@ 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 minicore = snap.minicore()?; + let mut highlight_config = snap.config.highlighting_config(minicore.as_deref()); // 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 +1990,8 @@ 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 minicore = snap.minicore()?; + let mut highlight_config = snap.config.highlighting_config(minicore.as_deref()); // 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; diff --git a/crates/rust-analyzer/src/main_loop.rs b/crates/rust-analyzer/src/main_loop.rs index c6762f318326..649e7d284490 100644 --- a/crates/rust-analyzer/src/main_loop.rs +++ b/crates/rust-analyzer/src/main_loop.rs @@ -847,6 +847,16 @@ impl GlobalState { self.debounce_workspace_fetch(); let vfs = &mut self.vfs.write().0; for (path, contents) in files { + if !is_changed + && self.minicore_file_id.is_none() + && matches!(path.name_and_extension(), Some(("minicore", Some("rs")))) + && let Some((minicore_file_id, vfs::FileExcluded::No)) = + vfs.file_id(&VfsPath::from(path.clone())) + { + // Not a lot of bad can happen from mistakenly identifying `minicore`, so proceed with that. + self.minicore_file_id = Some(minicore_file_id); + } + let path = VfsPath::from(path); // if the file is in mem docs, it's managed by the client via notifications // so only set it if its not in there diff --git a/crates/test-fixture/src/lib.rs b/crates/test-fixture/src/lib.rs index 7574d12c0cfd..8f21428393ec 100644 --- a/crates/test-fixture/src/lib.rs +++ b/crates/test-fixture/src/lib.rs @@ -24,7 +24,7 @@ use paths::AbsPathBuf; use span::{Edition, FileId, Span}; use stdx::itertools::Itertools; use test_utils::{ - CURSOR_MARKER, ESCAPED_CURSOR_MARKER, Fixture, FixtureWithProjectMeta, RangeOrOffset, + CURSOR_MARKER, ESCAPED_CURSOR_MARKER, Fixture, FixtureWithProjectMeta, MiniCore, RangeOrOffset, extract_range_or_offset, }; use triomphe::Arc; @@ -69,7 +69,12 @@ pub trait WithFixture: Default + ExpandDatabase + SourceDatabase + 'static { proc_macros: Vec<(String, ProcMacro)>, ) -> Self { let mut db = Self::default(); - let fixture = ChangeFixture::parse_with_proc_macros(&db, ra_fixture, proc_macros); + let fixture = ChangeFixture::parse_with_proc_macros( + &db, + ra_fixture, + MiniCore::RAW_SOURCE, + proc_macros, + ); fixture.change.apply(&mut db); assert!(fixture.file_position.is_none()); db @@ -112,6 +117,7 @@ impl WithFixture for DB pub struct ChangeFixture { pub file_position: Option<(EditionedFileId, RangeOrOffset)>, + pub file_lines: Vec, pub files: Vec, pub change: ChangeWithProcMacros, } @@ -123,12 +129,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 +156,7 @@ impl ChangeFixture { let mut source_change = FileChange::default(); let mut 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 +181,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 +269,7 @@ 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())); + source_change.change_file(core_file, Some(mini_core.source_code(minicore_raw))); let core_crate = crate_graph.add_crate_root( core_file, @@ -394,7 +404,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 } } } diff --git a/crates/test-utils/src/fixture.rs b/crates/test-utils/src/fixture.rs index c024089a016f..52f804df1b10 100644 --- a/crates/test-utils/src/fixture.rs +++ b/crates/test-utils/src/fixture.rs @@ -132,13 +132,17 @@ pub struct Fixture { pub library: bool, /// Actual file contents. All meta comments are stripped. pub text: String, + /// The line number in the original fixture of the beginning of this fixture. + pub line: usize, } +#[derive(Debug)] pub struct MiniCore { activated_flags: Vec, valid_flags: Vec, } +#[derive(Debug)] pub struct FixtureWithProjectMeta { pub fixture: Vec, pub mini_core: Option, @@ -184,40 +188,49 @@ impl FixtureWithProjectMeta { let mut mini_core = None; let mut res: Vec = Vec::new(); let mut proc_macro_names = vec![]; + let mut first_row = 0; if let Some(meta) = fixture.strip_prefix("//- toolchain:") { + first_row += 1; let (meta, remain) = meta.split_once('\n').unwrap(); toolchain = Some(meta.trim().to_owned()); fixture = remain; } if let Some(meta) = fixture.strip_prefix("//- target_data_layout:") { + first_row += 1; let (meta, remain) = meta.split_once('\n').unwrap(); meta.trim().clone_into(&mut target_data_layout); fixture = remain; } if let Some(meta) = fixture.strip_prefix("//- target_arch:") { + first_row += 1; let (meta, remain) = meta.split_once('\n').unwrap(); meta.trim().clone_into(&mut target_arch); fixture = remain; } if let Some(meta) = fixture.strip_prefix("//- proc_macros:") { + first_row += 1; let (meta, remain) = meta.split_once('\n').unwrap(); proc_macro_names = meta.split(',').map(|it| it.trim().to_owned()).collect(); fixture = remain; } if let Some(meta) = fixture.strip_prefix("//- minicore:") { + first_row += 1; let (meta, remain) = meta.split_once('\n').unwrap(); mini_core = Some(MiniCore::parse(meta)); fixture = remain; } - let default = if fixture.contains("//-") { None } else { Some("//- /main.rs") }; + let default = + if fixture.contains("//-") { None } else { Some((first_row - 1, "//- /main.rs")) }; - for (ix, line) in default.into_iter().chain(fixture.split_inclusive('\n')).enumerate() { + for (ix, line) in + default.into_iter().chain((first_row..).zip(fixture.split_inclusive('\n'))) + { if line.contains("//-") { assert!( line.starts_with("//-"), @@ -228,7 +241,7 @@ impl FixtureWithProjectMeta { } if let Some(line) = line.strip_prefix("//-") { - let meta = Self::parse_meta_line(line); + let meta = Self::parse_meta_line(line, (ix + 1).try_into().unwrap()); res.push(meta); } else { if matches!(line.strip_prefix("// "), Some(l) if l.trim().starts_with('/')) { @@ -252,7 +265,7 @@ impl FixtureWithProjectMeta { } //- /lib.rs crate:foo deps:bar,baz cfg:foo=a,bar=b env:OUTDIR=path/to,OTHER=foo - fn parse_meta_line(meta: &str) -> Fixture { + fn parse_meta_line(meta: &str, line: usize) -> Fixture { let meta = meta.trim(); let mut components = meta.split_ascii_whitespace(); @@ -317,6 +330,7 @@ impl FixtureWithProjectMeta { Fixture { path, text: String::new(), + line, krate, deps, extern_prelude, @@ -330,7 +344,7 @@ impl FixtureWithProjectMeta { } impl MiniCore { - const RAW_SOURCE: &'static str = include_str!("./minicore.rs"); + pub const RAW_SOURCE: &'static str = include_str!("./minicore.rs"); fn has_flag(&self, flag: &str) -> bool { self.activated_flags.iter().any(|it| it == flag) @@ -363,8 +377,8 @@ impl MiniCore { res } - pub fn available_flags() -> impl Iterator { - let lines = MiniCore::RAW_SOURCE.split_inclusive('\n'); + pub fn available_flags(raw_source: &str) -> impl Iterator { + let lines = raw_source.split_inclusive('\n'); lines .map_while(|x| x.strip_prefix("//!")) .skip_while(|line| !line.contains("Available flags:")) @@ -375,9 +389,9 @@ impl MiniCore { /// Strips parts of minicore.rs which are flagged by inactive flags. /// /// This is probably over-engineered to support flags dependencies. - pub fn source_code(mut self) -> String { + pub fn source_code(mut self, raw_source: &str) -> String { let mut buf = String::new(); - let mut lines = MiniCore::RAW_SOURCE.split_inclusive('\n'); + let mut lines = raw_source.split_inclusive('\n'); let mut implications = Vec::new();