mirror of
https://github.com/rust-lang/rust-analyzer.git
synced 2025-10-03 07:04:49 +00:00
Merge commit '21b06c1beb
' into sync-from-ra
This commit is contained in:
parent
cac74d98f6
commit
e37cf75791
59 changed files with 1080 additions and 477 deletions
|
@ -42,6 +42,7 @@ tracing-tree.workspace = true
|
|||
triomphe.workspace = true
|
||||
nohash-hasher.workspace = true
|
||||
always-assert = "0.1.2"
|
||||
walkdir = "2.3.2"
|
||||
|
||||
cfg.workspace = true
|
||||
flycheck.workspace = true
|
||||
|
|
|
@ -87,6 +87,7 @@ fn main() -> anyhow::Result<()> {
|
|||
flags::RustAnalyzerCmd::Lsif(cmd) => cmd.run()?,
|
||||
flags::RustAnalyzerCmd::Scip(cmd) => cmd.run()?,
|
||||
flags::RustAnalyzerCmd::RunTests(cmd) => cmd.run()?,
|
||||
flags::RustAnalyzerCmd::RustcTests(cmd) => cmd.run()?,
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
|
|
@ -10,6 +10,7 @@ mod ssr;
|
|||
mod lsif;
|
||||
mod scip;
|
||||
mod run_tests;
|
||||
mod rustc_tests;
|
||||
|
||||
mod progress_report;
|
||||
|
||||
|
|
|
@ -98,6 +98,15 @@ xflags::xflags! {
|
|||
required path: PathBuf
|
||||
}
|
||||
|
||||
/// Run unit tests of the project using mir interpreter
|
||||
cmd rustc-tests {
|
||||
/// Directory with Cargo.toml.
|
||||
required rustc_repo: PathBuf
|
||||
|
||||
/// Only run tests with filter as substring
|
||||
optional --filter path: String
|
||||
}
|
||||
|
||||
cmd diagnostics {
|
||||
/// Directory with Cargo.toml.
|
||||
required path: PathBuf
|
||||
|
@ -159,6 +168,7 @@ pub enum RustAnalyzerCmd {
|
|||
Highlight(Highlight),
|
||||
AnalysisStats(AnalysisStats),
|
||||
RunTests(RunTests),
|
||||
RustcTests(RustcTests),
|
||||
Diagnostics(Diagnostics),
|
||||
Ssr(Ssr),
|
||||
Search(Search),
|
||||
|
@ -211,6 +221,12 @@ pub struct RunTests {
|
|||
pub path: PathBuf,
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct RustcTests {
|
||||
pub rustc_repo: PathBuf,
|
||||
pub filter: Option<String>,
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct Diagnostics {
|
||||
pub path: PathBuf,
|
||||
|
|
236
crates/rust-analyzer/src/cli/rustc_tests.rs
Normal file
236
crates/rust-analyzer/src/cli/rustc_tests.rs
Normal file
|
@ -0,0 +1,236 @@
|
|||
//! Run all tests in a project, similar to `cargo test`, but using the mir interpreter.
|
||||
|
||||
use std::{
|
||||
cell::RefCell, collections::HashMap, fs::read_to_string, panic::AssertUnwindSafe, path::PathBuf,
|
||||
};
|
||||
|
||||
use hir::Crate;
|
||||
use ide::{AnalysisHost, Change, DiagnosticCode, DiagnosticsConfig};
|
||||
use profile::StopWatch;
|
||||
use project_model::{CargoConfig, ProjectWorkspace, RustLibSource, Sysroot};
|
||||
|
||||
use load_cargo::{load_workspace, LoadCargoConfig, ProcMacroServerChoice};
|
||||
use triomphe::Arc;
|
||||
use vfs::{AbsPathBuf, FileId};
|
||||
use walkdir::WalkDir;
|
||||
|
||||
use crate::cli::{flags, report_metric, Result};
|
||||
|
||||
struct Tester {
|
||||
host: AnalysisHost,
|
||||
root_file: FileId,
|
||||
pass_count: u64,
|
||||
ignore_count: u64,
|
||||
fail_count: u64,
|
||||
stopwatch: StopWatch,
|
||||
}
|
||||
|
||||
fn string_to_diagnostic_code_leaky(code: &str) -> DiagnosticCode {
|
||||
thread_local! {
|
||||
static LEAK_STORE: RefCell<HashMap<String, DiagnosticCode>> = RefCell::new(HashMap::new());
|
||||
}
|
||||
LEAK_STORE.with_borrow_mut(|s| match s.get(code) {
|
||||
Some(c) => *c,
|
||||
None => {
|
||||
let v = DiagnosticCode::RustcHardError(format!("E{code}").leak());
|
||||
s.insert(code.to_owned(), v);
|
||||
v
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
fn detect_errors_from_rustc_stderr_file(p: PathBuf) -> HashMap<DiagnosticCode, usize> {
|
||||
let text = read_to_string(p).unwrap();
|
||||
let mut result = HashMap::new();
|
||||
{
|
||||
let mut text = &*text;
|
||||
while let Some(p) = text.find("error[E") {
|
||||
text = &text[p + 7..];
|
||||
let code = string_to_diagnostic_code_leaky(&text[..4]);
|
||||
*result.entry(code).or_insert(0) += 1;
|
||||
}
|
||||
}
|
||||
result
|
||||
}
|
||||
|
||||
impl Tester {
|
||||
fn new() -> Result<Self> {
|
||||
let tmp_file = AbsPathBuf::assert("/tmp/ra-rustc-test.rs".into());
|
||||
std::fs::write(&tmp_file, "")?;
|
||||
let mut cargo_config = CargoConfig::default();
|
||||
cargo_config.sysroot = Some(RustLibSource::Discover);
|
||||
let workspace = ProjectWorkspace::DetachedFiles {
|
||||
files: vec![tmp_file.clone()],
|
||||
sysroot: Ok(
|
||||
Sysroot::discover(tmp_file.parent().unwrap(), &cargo_config.extra_env).unwrap()
|
||||
),
|
||||
rustc_cfg: vec![],
|
||||
};
|
||||
let load_cargo_config = LoadCargoConfig {
|
||||
load_out_dirs_from_check: false,
|
||||
with_proc_macro_server: ProcMacroServerChoice::Sysroot,
|
||||
prefill_caches: false,
|
||||
};
|
||||
let (host, _vfs, _proc_macro) =
|
||||
load_workspace(workspace, &cargo_config.extra_env, &load_cargo_config)?;
|
||||
let db = host.raw_database();
|
||||
let krates = Crate::all(db);
|
||||
let root_crate = krates.iter().cloned().find(|krate| krate.origin(db).is_local()).unwrap();
|
||||
let root_file = root_crate.root_file(db);
|
||||
Ok(Self {
|
||||
host,
|
||||
root_file,
|
||||
pass_count: 0,
|
||||
ignore_count: 0,
|
||||
fail_count: 0,
|
||||
stopwatch: StopWatch::start(),
|
||||
})
|
||||
}
|
||||
|
||||
fn test(&mut self, p: PathBuf) {
|
||||
if p.parent().unwrap().file_name().unwrap() == "auxiliary" {
|
||||
// These are not tests
|
||||
return;
|
||||
}
|
||||
if IGNORED_TESTS.iter().any(|ig| p.file_name().is_some_and(|x| x == *ig)) {
|
||||
println!("{p:?} IGNORE");
|
||||
self.ignore_count += 1;
|
||||
return;
|
||||
}
|
||||
let stderr_path = p.with_extension("stderr");
|
||||
let expected = if stderr_path.exists() {
|
||||
detect_errors_from_rustc_stderr_file(stderr_path)
|
||||
} else {
|
||||
HashMap::new()
|
||||
};
|
||||
let text = read_to_string(&p).unwrap();
|
||||
let mut change = Change::new();
|
||||
// Ignore unstable tests, since they move too fast and we do not intend to support all of them.
|
||||
let mut ignore_test = text.contains("#![feature");
|
||||
// Ignore test with extern crates, as this infra don't support them yet.
|
||||
ignore_test |= text.contains("// aux-build:") || text.contains("// aux-crate:");
|
||||
// Ignore test with extern modules similarly.
|
||||
ignore_test |= text.contains("mod ");
|
||||
// These should work, but they don't, and I don't know why, so ignore them.
|
||||
ignore_test |= text.contains("extern crate proc_macro");
|
||||
let should_have_no_error = text.contains("// check-pass")
|
||||
|| text.contains("// build-pass")
|
||||
|| text.contains("// run-pass");
|
||||
change.change_file(self.root_file, Some(Arc::from(text)));
|
||||
self.host.apply_change(change);
|
||||
let diagnostic_config = DiagnosticsConfig::test_sample();
|
||||
let diags = self
|
||||
.host
|
||||
.analysis()
|
||||
.diagnostics(&diagnostic_config, ide::AssistResolveStrategy::None, self.root_file)
|
||||
.unwrap();
|
||||
let mut actual = HashMap::new();
|
||||
for diag in diags {
|
||||
if !matches!(diag.code, DiagnosticCode::RustcHardError(_)) {
|
||||
continue;
|
||||
}
|
||||
if !should_have_no_error && !SUPPORTED_DIAGNOSTICS.contains(&diag.code) {
|
||||
continue;
|
||||
}
|
||||
*actual.entry(diag.code).or_insert(0) += 1;
|
||||
}
|
||||
// Ignore tests with diagnostics that we don't emit.
|
||||
ignore_test |= expected.keys().any(|k| !SUPPORTED_DIAGNOSTICS.contains(k));
|
||||
if ignore_test {
|
||||
println!("{p:?} IGNORE");
|
||||
self.ignore_count += 1;
|
||||
} else if actual == expected {
|
||||
println!("{p:?} PASS");
|
||||
self.pass_count += 1;
|
||||
} else {
|
||||
println!("{p:?} FAIL");
|
||||
println!("actual (r-a) = {:?}", actual);
|
||||
println!("expected (rustc) = {:?}", expected);
|
||||
self.fail_count += 1;
|
||||
}
|
||||
}
|
||||
|
||||
fn report(&mut self) {
|
||||
println!(
|
||||
"Pass count = {}, Fail count = {}, Ignore count = {}",
|
||||
self.pass_count, self.fail_count, self.ignore_count
|
||||
);
|
||||
println!("Testing time and memory = {}", self.stopwatch.elapsed());
|
||||
report_metric("rustc failed tests", self.fail_count, "#");
|
||||
report_metric("rustc testing time", self.stopwatch.elapsed().time.as_millis() as u64, "ms");
|
||||
}
|
||||
}
|
||||
|
||||
/// These tests break rust-analyzer (either by panicking or hanging) so we should ignore them.
|
||||
const IGNORED_TESTS: &[&str] = &[
|
||||
"trait-with-missing-associated-type-restriction.rs", // #15646
|
||||
"trait-with-missing-associated-type-restriction-fixable.rs", // #15646
|
||||
"resolve-self-in-impl.rs",
|
||||
"basic.rs", // ../rust/tests/ui/associated-type-bounds/return-type-notation/basic.rs
|
||||
"issue-26056.rs",
|
||||
"float-field.rs",
|
||||
"invalid_operator_trait.rs",
|
||||
"type-alias-impl-trait-assoc-dyn.rs",
|
||||
"deeply-nested_closures.rs", // exponential time
|
||||
"hang-on-deeply-nested-dyn.rs", // exponential time
|
||||
"dyn-rpit-and-let.rs", // unexpected free variable with depth `^1.0` with outer binder ^0
|
||||
"issue-16098.rs", // Huge recursion limit for macros?
|
||||
"issue-83471.rs", // crates/hir-ty/src/builder.rs:78:9: assertion failed: self.remaining() > 0
|
||||
];
|
||||
|
||||
const SUPPORTED_DIAGNOSTICS: &[DiagnosticCode] = &[
|
||||
DiagnosticCode::RustcHardError("E0023"),
|
||||
DiagnosticCode::RustcHardError("E0046"),
|
||||
DiagnosticCode::RustcHardError("E0063"),
|
||||
DiagnosticCode::RustcHardError("E0107"),
|
||||
DiagnosticCode::RustcHardError("E0117"),
|
||||
DiagnosticCode::RustcHardError("E0133"),
|
||||
DiagnosticCode::RustcHardError("E0210"),
|
||||
DiagnosticCode::RustcHardError("E0268"),
|
||||
DiagnosticCode::RustcHardError("E0308"),
|
||||
DiagnosticCode::RustcHardError("E0384"),
|
||||
DiagnosticCode::RustcHardError("E0407"),
|
||||
DiagnosticCode::RustcHardError("E0432"),
|
||||
DiagnosticCode::RustcHardError("E0451"),
|
||||
DiagnosticCode::RustcHardError("E0507"),
|
||||
DiagnosticCode::RustcHardError("E0583"),
|
||||
DiagnosticCode::RustcHardError("E0559"),
|
||||
DiagnosticCode::RustcHardError("E0616"),
|
||||
DiagnosticCode::RustcHardError("E0618"),
|
||||
DiagnosticCode::RustcHardError("E0624"),
|
||||
DiagnosticCode::RustcHardError("E0774"),
|
||||
DiagnosticCode::RustcHardError("E0767"),
|
||||
DiagnosticCode::RustcHardError("E0777"),
|
||||
];
|
||||
|
||||
impl flags::RustcTests {
|
||||
pub fn run(self) -> Result<()> {
|
||||
let mut tester = Tester::new()?;
|
||||
let walk_dir = WalkDir::new(self.rustc_repo.join("tests/ui"));
|
||||
for i in walk_dir {
|
||||
let i = i?;
|
||||
let p = i.into_path();
|
||||
if let Some(f) = &self.filter {
|
||||
if !p.as_os_str().to_string_lossy().contains(f) {
|
||||
continue;
|
||||
}
|
||||
}
|
||||
if p.extension().map_or(true, |x| x != "rs") {
|
||||
continue;
|
||||
}
|
||||
if let Err(e) = std::panic::catch_unwind({
|
||||
let tester = AssertUnwindSafe(&mut tester);
|
||||
let p = p.clone();
|
||||
move || {
|
||||
let tester = tester;
|
||||
tester.0.test(p);
|
||||
}
|
||||
}) {
|
||||
println!("panic detected at test {:?}", p);
|
||||
std::panic::resume_unwind(e);
|
||||
}
|
||||
}
|
||||
tester.report();
|
||||
Ok(())
|
||||
}
|
||||
}
|
|
@ -1354,6 +1354,7 @@ impl Config {
|
|||
}
|
||||
}
|
||||
|
||||
// FIXME: This should be an AbsolutePathBuf
|
||||
fn target_dir_from_config(&self) -> Option<PathBuf> {
|
||||
self.data.rust_analyzerTargetDir.as_ref().and_then(|target_dir| match target_dir {
|
||||
TargetDirectory::UseSubdirectory(yes) if *yes => {
|
||||
|
|
|
@ -12,8 +12,8 @@ use anyhow::Context;
|
|||
|
||||
use ide::{
|
||||
AnnotationConfig, AssistKind, AssistResolveStrategy, Cancellable, FilePosition, FileRange,
|
||||
HoverAction, HoverGotoTypeData, InlayFieldsToResolve, Query, RangeInfo, ReferenceCategory,
|
||||
Runnable, RunnableKind, SingleResolve, SourceChange, TextEdit,
|
||||
HoverAction, HoverGotoTypeData, InlayFieldsToResolve, Query, RangeInfo, RangeLimit,
|
||||
ReferenceCategory, Runnable, RunnableKind, SingleResolve, SourceChange, TextEdit,
|
||||
};
|
||||
use ide_db::SymbolKind;
|
||||
use lsp_server::ErrorCode;
|
||||
|
@ -1409,7 +1409,7 @@ pub(crate) fn handle_inlay_hints(
|
|||
let inlay_hints_config = snap.config.inlay_hints();
|
||||
Ok(Some(
|
||||
snap.analysis
|
||||
.inlay_hints(&inlay_hints_config, file_id, Some(range))?
|
||||
.inlay_hints(&inlay_hints_config, file_id, Some(RangeLimit::Fixed(range)))?
|
||||
.into_iter()
|
||||
.map(|it| {
|
||||
to_proto::inlay_hint(
|
||||
|
@ -1440,22 +1440,13 @@ pub(crate) fn handle_inlay_hints_resolve(
|
|||
anyhow::ensure!(snap.file_exists(file_id), "Invalid LSP resolve data");
|
||||
|
||||
let line_index = snap.file_line_index(file_id)?;
|
||||
let range = from_proto::text_range(
|
||||
&line_index,
|
||||
lsp_types::Range { start: original_hint.position, end: original_hint.position },
|
||||
)?;
|
||||
let range_start = range.start();
|
||||
let range_end = range.end();
|
||||
let large_range = TextRange::new(
|
||||
range_start.checked_sub(1.into()).unwrap_or(range_start),
|
||||
range_end.checked_add(1.into()).unwrap_or(range_end),
|
||||
);
|
||||
let hint_position = from_proto::offset(&line_index, original_hint.position)?;
|
||||
let mut forced_resolve_inlay_hints_config = snap.config.inlay_hints();
|
||||
forced_resolve_inlay_hints_config.fields_to_resolve = InlayFieldsToResolve::empty();
|
||||
let resolve_hints = snap.analysis.inlay_hints(
|
||||
&forced_resolve_inlay_hints_config,
|
||||
file_id,
|
||||
Some(large_range),
|
||||
Some(RangeLimit::NearestParent(hint_position)),
|
||||
)?;
|
||||
|
||||
let mut resolved_hints = resolve_hints
|
||||
|
|
|
@ -32,7 +32,10 @@ fn integrated_highlighting_benchmark() {
|
|||
let workspace_to_load = project_root();
|
||||
let file = "./crates/rust-analyzer/src/config.rs";
|
||||
|
||||
let cargo_config = CargoConfig::default();
|
||||
let cargo_config = CargoConfig {
|
||||
sysroot: Some(project_model::RustLibSource::Discover),
|
||||
..CargoConfig::default()
|
||||
};
|
||||
let load_cargo_config = LoadCargoConfig {
|
||||
load_out_dirs_from_check: true,
|
||||
with_proc_macro_server: ProcMacroServerChoice::None,
|
||||
|
@ -85,7 +88,10 @@ fn integrated_completion_benchmark() {
|
|||
let workspace_to_load = project_root();
|
||||
let file = "./crates/hir/src/lib.rs";
|
||||
|
||||
let cargo_config = CargoConfig::default();
|
||||
let cargo_config = CargoConfig {
|
||||
sysroot: Some(project_model::RustLibSource::Discover),
|
||||
..CargoConfig::default()
|
||||
};
|
||||
let load_cargo_config = LoadCargoConfig {
|
||||
load_out_dirs_from_check: true,
|
||||
with_proc_macro_server: ProcMacroServerChoice::None,
|
||||
|
@ -103,10 +109,46 @@ fn integrated_completion_benchmark() {
|
|||
vfs.file_id(&path).unwrap_or_else(|| panic!("can't find virtual file for {path}"))
|
||||
};
|
||||
|
||||
// kick off parsing and index population
|
||||
|
||||
let completion_offset = {
|
||||
let _it = stdx::timeit("change");
|
||||
let mut text = host.analysis().file_text(file_id).unwrap().to_string();
|
||||
let completion_offset =
|
||||
patch(&mut text, "db.struct_data(self.id)", "sel;\ndb.struct_data(self.id)")
|
||||
+ "sel".len();
|
||||
let mut change = Change::new();
|
||||
change.change_file(file_id, Some(Arc::from(text)));
|
||||
host.apply_change(change);
|
||||
completion_offset
|
||||
};
|
||||
|
||||
{
|
||||
let _it = stdx::timeit("initial");
|
||||
let _span = profile::cpu_span();
|
||||
let analysis = host.analysis();
|
||||
analysis.highlight_as_html(file_id, false).unwrap();
|
||||
let config = CompletionConfig {
|
||||
enable_postfix_completions: true,
|
||||
enable_imports_on_the_fly: true,
|
||||
enable_self_on_the_fly: true,
|
||||
enable_private_editable: true,
|
||||
full_function_signatures: false,
|
||||
callable: Some(CallableSnippets::FillArguments),
|
||||
snippet_cap: SnippetCap::new(true),
|
||||
insert_use: InsertUseConfig {
|
||||
granularity: ImportGranularity::Crate,
|
||||
prefix_kind: hir::PrefixKind::ByCrate,
|
||||
enforce_granularity: true,
|
||||
group: true,
|
||||
skip_glob_imports: true,
|
||||
},
|
||||
snippets: Vec::new(),
|
||||
prefer_no_std: false,
|
||||
prefer_prelude: true,
|
||||
limit: None,
|
||||
};
|
||||
let position =
|
||||
FilePosition { file_id, offset: TextSize::try_from(completion_offset).unwrap() };
|
||||
analysis.completions(&config, position, None).unwrap();
|
||||
}
|
||||
|
||||
profile::init_from("*>5");
|
||||
|
@ -116,8 +158,8 @@ fn integrated_completion_benchmark() {
|
|||
let _it = stdx::timeit("change");
|
||||
let mut text = host.analysis().file_text(file_id).unwrap().to_string();
|
||||
let completion_offset =
|
||||
patch(&mut text, "db.struct_data(self.id)", "sel;\ndb.struct_data(self.id)")
|
||||
+ "sel".len();
|
||||
patch(&mut text, "sel;\ndb.struct_data(self.id)", ";sel;\ndb.struct_data(self.id)")
|
||||
+ ";sel".len();
|
||||
let mut change = Change::new();
|
||||
change.change_file(file_id, Some(Arc::from(text)));
|
||||
host.apply_change(change);
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue