Merge commit '37f84c101b' into sync-from-ra

This commit is contained in:
Laurențiu Nicola 2023-07-17 16:49:15 +03:00
parent 6502421771
commit 4704881b64
311 changed files with 13700 additions and 9110 deletions

View file

@ -34,10 +34,9 @@ serde.workspace = true
rayon = "1.6.1"
num_cpus = "1.15.0"
mimalloc = { version = "0.1.30", default-features = false, optional = true }
lsp-server = { version = "0.7.0", path = "../../lib/lsp-server" }
lsp-server.workspace = true
tracing = "0.1.35"
tracing-subscriber = { version = "0.3.16", default-features = false, features = [
"env-filter",
"registry",
"fmt",
"tracing-log",
@ -48,12 +47,8 @@ triomphe.workspace = true
nohash-hasher.workspace = true
always-assert = "0.1.2"
# These dependencies are unused, but we pin them to a version here to restrict them for our transitive dependencies
# so that we don't pull in duplicates of their dependencies like windows-sys and syn 1 vs 2
# these would pull in serde 2
thiserror = "=1.0.39"
serde_repr = "=0.1.11"
# these would pull in windows-sys 0.45.0
# These 3 deps are not used by r-a directly, but we list them here to lock in their versions
# in our transitive deps to prevent them from pulling in windows-sys 0.45.0
mio = "=0.8.5"
filetime = "=0.2.19"
parking_lot_core = "=0.9.6"
@ -67,13 +62,13 @@ ide-db.workspace = true
# This should only be used in CLI
ide-ssr.workspace = true
ide.workspace = true
load-cargo.workspace = true
proc-macro-api.workspace = true
profile.workspace = true
project-model.workspace = true
stdx.workspace = true
syntax.workspace = true
toolchain.workspace = true
tt.workspace = true
vfs-notify.workspace = true
vfs.workspace = true

View file

@ -8,10 +8,11 @@ use std::{
sync::Arc,
};
use rust_analyzer::Result;
use anyhow::Context;
use tracing::{level_filters::LevelFilter, Event, Subscriber};
use tracing_log::NormalizeEvent;
use tracing_subscriber::{
filter::Targets,
fmt::{
format::Writer, writer::BoxMakeWriter, FmtContext, FormatEvent, FormatFields,
FormattedFields, MakeWriter,
@ -19,81 +20,62 @@ use tracing_subscriber::{
layer::SubscriberExt,
registry::LookupSpan,
util::SubscriberInitExt,
EnvFilter, Registry,
Registry,
};
use tracing_tree::HierarchicalLayer;
pub(crate) struct Logger {
filter: EnvFilter,
file: Option<File>,
pub(crate) struct LoggerConfig {
pub(crate) log_file: Option<File>,
pub(crate) filter: String,
pub(crate) chalk_filter: Option<String>,
}
struct MakeWriterStderr;
impl<'a> MakeWriter<'a> for MakeWriterStderr {
impl MakeWriter<'_> for MakeWriterStderr {
type Writer = Stderr;
fn make_writer(&'a self) -> Self::Writer {
fn make_writer(&self) -> Self::Writer {
io::stderr()
}
}
impl Logger {
pub(crate) fn new(file: Option<File>, filter: Option<&str>) -> Logger {
let filter = filter.map_or(EnvFilter::default(), EnvFilter::new);
impl LoggerConfig {
pub(crate) fn init(self) -> anyhow::Result<()> {
let mut filter: Targets = self
.filter
.parse()
.with_context(|| format!("invalid log filter: `{}`", self.filter))?;
Logger { filter, file }
}
let mut chalk_layer = None;
if let Some(chalk_filter) = self.chalk_filter {
let level: LevelFilter =
chalk_filter.parse().with_context(|| "invalid chalk log filter")?;
chalk_layer = Some(
HierarchicalLayer::default()
.with_indent_lines(true)
.with_ansi(false)
.with_indent_amount(2)
.with_writer(io::stderr),
);
filter = filter
.with_target("chalk_solve", level)
.with_target("chalk_ir", level)
.with_target("chalk_recursive", level);
};
pub(crate) fn install(self) -> Result<()> {
// The meaning of CHALK_DEBUG I suspected is to tell chalk crates
// (i.e. chalk-solve, chalk-ir, chalk-recursive) how to filter tracing
// logs. But now we can only have just one filter, which means we have to
// merge chalk filter to our main filter (from RA_LOG env).
//
// The acceptable syntax of CHALK_DEBUG is `target[span{field=value}]=level`.
// As the value should only affect chalk crates, we'd better manually
// specify the target. And for simplicity, CHALK_DEBUG only accept the value
// that specify level.
let chalk_level_dir = std::env::var("CHALK_DEBUG")
.map(|val| {
val.parse::<LevelFilter>().expect(
"invalid CHALK_DEBUG value, expect right log level (like debug or trace)",
)
})
.ok();
let chalk_layer = HierarchicalLayer::default()
.with_indent_lines(true)
.with_ansi(false)
.with_indent_amount(2)
.with_writer(io::stderr);
let writer = match self.file {
let writer = match self.log_file {
Some(file) => BoxMakeWriter::new(Arc::new(file)),
None => BoxMakeWriter::new(io::stderr),
};
let ra_fmt_layer =
tracing_subscriber::fmt::layer().event_format(LoggerFormatter).with_writer(writer);
match chalk_level_dir {
Some(val) => {
Registry::default()
.with(
self.filter
.add_directive(format!("chalk_solve={val}").parse()?)
.add_directive(format!("chalk_ir={val}").parse()?)
.add_directive(format!("chalk_recursive={val}").parse()?),
)
.with(ra_fmt_layer)
.with(chalk_layer)
.init();
}
None => {
Registry::default().with(self.filter).with(ra_fmt_layer).init();
}
};
let registry = Registry::default().with(filter).with(ra_fmt_layer);
match chalk_layer {
Some(chalk_layer) => registry.with(chalk_layer).init(),
None => registry.init(),
}
Ok(())
}
}

View file

@ -7,14 +7,11 @@
mod logger;
mod rustc_wrapper;
use std::{
env, fs,
path::{Path, PathBuf},
process,
};
use std::{env, fs, path::PathBuf, process};
use anyhow::Context;
use lsp_server::Connection;
use rust_analyzer::{cli::flags, config::Config, from_json, Result};
use rust_analyzer::{cli::flags, config::Config, from_json};
use vfs::AbsPathBuf;
#[cfg(all(feature = "mimalloc"))]
@ -25,7 +22,7 @@ static ALLOC: mimalloc::MiMalloc = mimalloc::MiMalloc;
#[global_allocator]
static ALLOC: jemallocator::Jemalloc = jemallocator::Jemalloc;
fn main() {
fn main() -> anyhow::Result<()> {
if std::env::var("RA_RUSTC_WRAPPER").is_ok() {
let mut args = std::env::args_os();
let _me = args.next().unwrap();
@ -41,14 +38,7 @@ fn main() {
}
let flags = flags::RustAnalyzer::from_env_or_exit();
if let Err(err) = try_main(flags) {
tracing::error!("Unexpected error: {}", err);
eprintln!("{err}");
process::exit(101);
}
}
fn try_main(flags: flags::RustAnalyzer) -> Result<()> {
#[cfg(debug_assertions)]
if flags.wait_dbg || env::var("RA_WAIT_DBG").is_ok() {
#[allow(unused_mut)]
@ -58,14 +48,8 @@ fn try_main(flags: flags::RustAnalyzer) -> Result<()> {
}
}
let mut log_file = flags.log_file.as_deref();
setup_logging(flags.log_file.clone())?;
let env_log_file = env::var("RA_LOG_FILE").ok();
if let Some(env_log_file) = env_log_file.as_deref() {
log_file = Some(Path::new(env_log_file));
}
setup_logging(log_file)?;
let verbosity = flags.verbosity();
match flags.subcommand {
@ -98,11 +82,12 @@ fn try_main(flags: flags::RustAnalyzer) -> Result<()> {
flags::RustAnalyzerCmd::Search(cmd) => cmd.run()?,
flags::RustAnalyzerCmd::Lsif(cmd) => cmd.run()?,
flags::RustAnalyzerCmd::Scip(cmd) => cmd.run()?,
flags::RustAnalyzerCmd::RunTests(cmd) => cmd.run()?,
}
Ok(())
}
fn setup_logging(log_file: Option<&Path>) -> Result<()> {
fn setup_logging(log_file_flag: Option<PathBuf>) -> anyhow::Result<()> {
if cfg!(windows) {
// This is required so that windows finds our pdb that is placed right beside the exe.
// By default it doesn't look at the folder the exe resides in, only in the current working
@ -115,23 +100,42 @@ fn setup_logging(log_file: Option<&Path>) -> Result<()> {
}
}
}
if env::var("RUST_BACKTRACE").is_err() {
env::set_var("RUST_BACKTRACE", "short");
}
let log_file = env::var("RA_LOG_FILE").ok().map(PathBuf::from).or(log_file_flag);
let log_file = match log_file {
Some(path) => {
if let Some(parent) = path.parent() {
let _ = fs::create_dir_all(parent);
}
Some(fs::File::create(path)?)
Some(
fs::File::create(&path)
.with_context(|| format!("can't create log file at {}", path.display()))?,
)
}
None => None,
};
let filter = env::var("RA_LOG").ok();
// deliberately enable all `error` logs if the user has not set RA_LOG, as there is usually useful
// information in there for debugging
logger::Logger::new(log_file, filter.as_deref().or(Some("error"))).install()?;
logger::LoggerConfig {
log_file,
// Deliberately enable all `error` logs if the user has not set RA_LOG, as there is usually
// useful information in there for debugging.
filter: env::var("RA_LOG").ok().unwrap_or_else(|| "error".to_string()),
// The meaning of CHALK_DEBUG I suspected is to tell chalk crates
// (i.e. chalk-solve, chalk-ir, chalk-recursive) how to filter tracing
// logs. But now we can only have just one filter, which means we have to
// merge chalk filter to our main filter (from RA_LOG env).
//
// The acceptable syntax of CHALK_DEBUG is `target[span{field=value}]=level`.
// As the value should only affect chalk crates, we'd better manually
// specify the target. And for simplicity, CHALK_DEBUG only accept the value
// that specify level.
chalk_filter: env::var("CHALK_DEBUG").ok(),
}
.init()?;
profile::init();
@ -146,8 +150,8 @@ const STACK_SIZE: usize = 1024 * 1024 * 8;
fn with_extra_thread(
thread_name: impl Into<String>,
thread_intent: stdx::thread::ThreadIntent,
f: impl FnOnce() -> Result<()> + Send + 'static,
) -> Result<()> {
f: impl FnOnce() -> anyhow::Result<()> + Send + 'static,
) -> anyhow::Result<()> {
let handle = stdx::thread::Builder::new(thread_intent)
.name(thread_name.into())
.stack_size(STACK_SIZE)
@ -158,7 +162,7 @@ fn with_extra_thread(
Ok(())
}
fn run_server() -> Result<()> {
fn run_server() -> anyhow::Result<()> {
tracing::info!("server version {} will start", rust_analyzer::version());
let (connection, io_threads) = Connection::stdio();

View file

@ -1,7 +1,6 @@
//! Various batch processing tasks, intended primarily for debugging.
pub mod flags;
pub mod load_cargo;
mod parse;
mod symbols;
mod highlight;
@ -10,13 +9,17 @@ mod diagnostics;
mod ssr;
mod lsif;
mod scip;
mod run_tests;
mod progress_report;
use std::io::Read;
use anyhow::Result;
use hir::{Module, Name};
use hir_ty::db::HirDatabase;
use ide::AnalysisHost;
use itertools::Itertools;
use vfs::Vfs;
#[derive(Clone, Copy)]
@ -36,7 +39,7 @@ impl Verbosity {
}
}
fn read_stdin() -> Result<String> {
fn read_stdin() -> anyhow::Result<String> {
let mut buff = String::new();
std::io::stdin().read_to_string(&mut buff)?;
Ok(buff)
@ -71,3 +74,14 @@ fn print_memory_usage(mut host: AnalysisHost, vfs: Vfs) {
eprintln!("{remaining:>8} Remaining");
}
fn full_name_of_item(db: &dyn HirDatabase, module: Module, name: Name) -> String {
module
.path_to_root(db)
.into_iter()
.rev()
.filter_map(|it| it.name(db))
.chain(Some(name))
.map(|it| it.display(db.upcast()).to_string())
.join("::")
}

View file

@ -15,7 +15,7 @@ use hir_def::{
hir::{ExprId, PatId},
};
use hir_ty::{Interner, Substitution, TyExt, TypeFlags};
use ide::{LineCol, RootDatabase};
use ide::{Analysis, AnnotationConfig, DiagnosticsConfig, InlayHintsConfig, LineCol, RootDatabase};
use ide_db::{
base_db::{
salsa::{self, debug::DebugQueryTable, ParallelDatabase},
@ -24,20 +24,20 @@ use ide_db::{
LineIndexDatabase,
};
use itertools::Itertools;
use load_cargo::{load_workspace, LoadCargoConfig, ProcMacroServerChoice};
use oorandom::Rand32;
use profile::{Bytes, StopWatch};
use project_model::{CargoConfig, ProjectManifest, ProjectWorkspace, RustLibSource};
use rayon::prelude::*;
use rustc_hash::FxHashSet;
use syntax::{AstNode, SyntaxNode};
use vfs::{AbsPathBuf, Vfs, VfsPath};
use vfs::{AbsPathBuf, FileId, Vfs, VfsPath};
use crate::cli::{
flags::{self, OutputFormat},
load_cargo::{load_workspace, LoadCargoConfig, ProcMacroServerChoice},
print_memory_usage,
full_name_of_item, print_memory_usage,
progress_report::ProgressReport,
report_metric, Result, Verbosity,
report_metric, Verbosity,
};
/// Need to wrap Snapshot to provide `Clone` impl for `map_with`
@ -49,7 +49,7 @@ impl<DB: ParallelDatabase> Clone for Snap<salsa::Snapshot<DB>> {
}
impl flags::AnalysisStats {
pub fn run(self, verbosity: Verbosity) -> Result<()> {
pub fn run(self, verbosity: Verbosity) -> anyhow::Result<()> {
let mut rng = {
let seed = SystemTime::now().duration_since(UNIX_EPOCH).unwrap().as_millis() as u64;
Rand32::new(seed)
@ -95,17 +95,41 @@ impl flags::AnalysisStats {
eprintln!(")");
let mut analysis_sw = self.stop_watch();
let mut num_crates = 0;
let mut visited_modules = FxHashSet::default();
let mut visit_queue = Vec::new();
let mut krates = Crate::all(db);
if self.randomize {
shuffle(&mut rng, &mut krates);
}
let mut item_tree_sw = self.stop_watch();
let mut num_item_trees = 0;
let source_roots =
krates.iter().cloned().map(|krate| db.file_source_root(krate.root_file(db))).unique();
for source_root_id in source_roots {
let source_root = db.source_root(source_root_id);
if !source_root.is_library || self.with_deps {
for file_id in source_root.iter() {
if let Some(p) = source_root.path_for_file(&file_id) {
if let Some((_, Some("rs"))) = p.name_and_extension() {
db.file_item_tree(file_id.into());
num_item_trees += 1;
}
}
}
}
}
eprintln!(" item trees: {num_item_trees}");
let item_tree_time = item_tree_sw.elapsed();
eprintln!("{:<20} {}", "Item Tree Collection:", item_tree_time);
report_metric("item tree time", item_tree_time.time.as_millis() as u64, "ms");
let mut crate_def_map_sw = self.stop_watch();
let mut num_crates = 0;
let mut visited_modules = FxHashSet::default();
let mut visit_queue = Vec::new();
for krate in krates {
let module = krate.root_module(db);
let file_id = module.definition_source(db).file_id;
let file_id = module.definition_source_file_id(db);
let file_id = file_id.original_file(db);
let source_root = db.file_source_root(file_id);
let source_root = db.source_root(source_root);
@ -124,8 +148,10 @@ impl flags::AnalysisStats {
let mut bodies = Vec::new();
let mut adts = Vec::new();
let mut consts = Vec::new();
let mut file_ids = Vec::new();
while let Some(module) = visit_queue.pop() {
if visited_modules.insert(module) {
file_ids.extend(module.as_source_file_id(db));
visit_queue.extend(module.children(db));
for decl in module.declarations(db) {
@ -171,7 +197,9 @@ impl flags::AnalysisStats {
adts.len(),
consts.len(),
);
eprintln!("{:<20} {}", "Item Collection:", analysis_sw.elapsed());
let crate_def_map_time = crate_def_map_sw.elapsed();
eprintln!("{:<20} {}", "Item Collection:", crate_def_map_time);
report_metric("crate def map time", crate_def_map_time.time.as_millis() as u64, "ms");
if self.randomize {
shuffle(&mut rng, &mut bodies);
@ -197,6 +225,10 @@ impl flags::AnalysisStats {
self.run_const_eval(db, &consts, verbosity);
}
if self.run_all_ide_things {
self.run_ide_things(host.analysis(), file_ids);
}
let total_span = analysis_sw.elapsed();
eprintln!("{:<20} {total_span}", "Total:");
report_metric("total time", total_span.time.as_millis() as u64, "ms");
@ -242,21 +274,15 @@ impl flags::AnalysisStats {
continue;
}
all += 1;
let Err(e)
= db.layout_of_adt(hir_def::AdtId::from(a).into(), Substitution::empty(Interner), a.krate(db).into())
else {
continue
let Err(e) = db.layout_of_adt(
hir_def::AdtId::from(a).into(),
Substitution::empty(Interner),
a.krate(db).into(),
) else {
continue;
};
if verbosity.is_spammy() {
let full_name = a
.module(db)
.path_to_root(db)
.into_iter()
.rev()
.filter_map(|it| it.name(db))
.chain(Some(a.name(db)))
.map(|it| it.display(db).to_string())
.join("::");
let full_name = full_name_of_item(db, a.module(db), a.name(db));
println!("Data layout for {full_name} failed due {e:?}");
}
fail += 1;
@ -278,15 +304,8 @@ impl flags::AnalysisStats {
continue;
};
if verbosity.is_spammy() {
let full_name = c
.module(db)
.path_to_root(db)
.into_iter()
.rev()
.filter_map(|it| it.name(db))
.chain(c.name(db))
.map(|it| it.display(db).to_string())
.join("::");
let full_name =
full_name_of_item(db, c.module(db), c.name(db).unwrap_or(Name::missing()));
println!("Const eval for {full_name} failed due {e:?}");
}
fail += 1;
@ -717,6 +736,83 @@ impl flags::AnalysisStats {
report_metric("body lowering time", body_lowering_time.time.as_millis() as u64, "ms");
}
fn run_ide_things(&self, analysis: Analysis, mut file_ids: Vec<FileId>) {
file_ids.sort();
file_ids.dedup();
let mut sw = self.stop_watch();
for &file_id in &file_ids {
_ = analysis.diagnostics(
&DiagnosticsConfig {
enabled: true,
proc_macros_enabled: true,
proc_attr_macros_enabled: true,
disable_experimental: false,
disabled: Default::default(),
expr_fill_default: Default::default(),
insert_use: ide_db::imports::insert_use::InsertUseConfig {
granularity: ide_db::imports::insert_use::ImportGranularity::Crate,
enforce_granularity: true,
prefix_kind: hir::PrefixKind::ByCrate,
group: true,
skip_glob_imports: true,
},
prefer_no_std: Default::default(),
},
ide::AssistResolveStrategy::All,
file_id,
);
}
for &file_id in &file_ids {
_ = analysis.inlay_hints(
&InlayHintsConfig {
render_colons: false,
type_hints: true,
discriminant_hints: ide::DiscriminantHints::Always,
parameter_hints: true,
chaining_hints: true,
adjustment_hints: ide::AdjustmentHints::Always,
adjustment_hints_mode: ide::AdjustmentHintsMode::Postfix,
adjustment_hints_hide_outside_unsafe: false,
closure_return_type_hints: ide::ClosureReturnTypeHints::Always,
closure_capture_hints: true,
binding_mode_hints: true,
lifetime_elision_hints: ide::LifetimeElisionHints::Always,
param_names_for_lifetime_elision_hints: true,
hide_named_constructor_hints: false,
hide_closure_initialization_hints: false,
closure_style: hir::ClosureStyle::ImplFn,
max_length: Some(25),
closing_brace_hints_min_lines: Some(20),
},
file_id,
None,
);
}
for &file_id in &file_ids {
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,
},
file_id,
)
.unwrap()
.into_iter()
.for_each(|annotation| {
_ = analysis.resolve_annotation(annotation);
});
}
let ide_time = sw.elapsed();
eprintln!("{:<20} {} ({} files)", "IDE:", ide_time, file_ids.len());
}
fn stop_watch(&self) -> StopWatch {
StopWatch::start().memory(self.memory_usage)
}

View file

@ -7,11 +7,9 @@ use rustc_hash::FxHashSet;
use hir::{db::HirDatabase, Crate, Module};
use ide::{AssistResolveStrategy, DiagnosticsConfig, Severity};
use ide_db::base_db::SourceDatabaseExt;
use load_cargo::{load_workspace_at, LoadCargoConfig, ProcMacroServerChoice};
use crate::cli::{
flags,
load_cargo::{load_workspace_at, LoadCargoConfig, ProcMacroServerChoice},
};
use crate::cli::flags;
impl flags::Diagnostics {
pub fn run(self) -> anyhow::Result<()> {
@ -37,14 +35,14 @@ impl flags::Diagnostics {
let mut visited_files = FxHashSet::default();
let work = all_modules(db).into_iter().filter(|module| {
let file_id = module.definition_source(db).file_id.original_file(db);
let file_id = module.definition_source_file_id(db).original_file(db);
let source_root = db.file_source_root(file_id);
let source_root = db.source_root(source_root);
!source_root.is_library
});
for module in work {
let file_id = module.definition_source(db).file_id.original_file(db);
let file_id = module.definition_source_file_id(db).original_file(db);
if !visited_files.contains(&file_id) {
let crate_name =
module.krate().display_name(db).as_deref().unwrap_or("unknown").to_string();

View file

@ -12,7 +12,7 @@ xflags::xflags! {
/// LSP server for the Rust programming language.
///
/// Subcommands and their flags do not provide any stability guarantees and may be removed or
/// changed without notice. Top-level flags that are not are marked as [Unstable] provide
/// changed without notice. Top-level flags that are not marked as [Unstable] provide
/// backwards-compatibility and may be relied on.
cmd rust-analyzer {
/// Verbosity level, can be repeated multiple times.
@ -88,6 +88,16 @@ xflags::xflags! {
optional --skip-data-layout
/// Skip const evaluation
optional --skip-const-eval
/// Runs several IDE features after analysis, including semantics highlighting, diagnostics
/// and annotations. This is useful for benchmarking the memory usage on a project that has
/// been worked on for a bit in a longer running session.
optional --run-all-ide-things
}
/// Run unit tests of the project using mir interpreter
cmd run-tests {
/// Directory with Cargo.toml.
required path: PathBuf
}
cmd diagnostics {
@ -103,7 +113,7 @@ xflags::xflags! {
}
cmd ssr {
/// A structured search replace rule (`$a.foo($b) ==> bar($a, $b)`)
/// A structured search replace rule (`$a.foo($b) ==>> bar($a, $b)`)
repeated rule: SsrRule
}
@ -147,6 +157,7 @@ pub enum RustAnalyzerCmd {
Symbols(Symbols),
Highlight(Highlight),
AnalysisStats(AnalysisStats),
RunTests(RunTests),
Diagnostics(Diagnostics),
Ssr(Ssr),
Search(Search),
@ -182,16 +193,22 @@ pub struct AnalysisStats {
pub parallel: bool,
pub memory_usage: bool,
pub source_stats: bool,
pub skip_lowering: bool,
pub skip_inference: bool,
pub skip_mir_stats: bool,
pub skip_data_layout: bool,
pub skip_const_eval: bool,
pub only: Option<String>,
pub with_deps: bool,
pub no_sysroot: bool,
pub disable_build_scripts: bool,
pub disable_proc_macros: bool,
pub skip_lowering: bool,
pub skip_inference: bool,
pub skip_mir_stats: bool,
pub skip_data_layout: bool,
pub skip_const_eval: bool,
pub run_all_ide_things: bool,
}
#[derive(Debug)]
pub struct RunTests {
pub path: PathBuf,
}
#[derive(Debug)]
@ -223,6 +240,7 @@ pub struct Lsif {
#[derive(Debug)]
pub struct Scip {
pub path: PathBuf,
pub output: Option<PathBuf>,
}

View file

@ -1,205 +0,0 @@
//! Loads a Cargo project into a static instance of analysis, without support
//! for incorporating changes.
use std::path::Path;
use anyhow::{anyhow, Result};
use crossbeam_channel::{unbounded, Receiver};
use ide::{AnalysisHost, Change};
use ide_db::{
base_db::{CrateGraph, ProcMacros},
FxHashMap,
};
use proc_macro_api::ProcMacroServer;
use project_model::{CargoConfig, ProjectManifest, ProjectWorkspace};
use triomphe::Arc;
use vfs::{loader::Handle, AbsPath, AbsPathBuf};
use crate::reload::{load_proc_macro, ProjectFolders, SourceRootConfig};
// Note: Since this type is used by external tools that use rust-analyzer as a library
// what otherwise would be `pub(crate)` has to be `pub` here instead.
pub struct LoadCargoConfig {
pub load_out_dirs_from_check: bool,
pub with_proc_macro_server: ProcMacroServerChoice,
pub prefill_caches: bool,
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum ProcMacroServerChoice {
Sysroot,
Explicit(AbsPathBuf),
None,
}
// Note: Since this function is used by external tools that use rust-analyzer as a library
// what otherwise would be `pub(crate)` has to be `pub` here instead.
pub fn load_workspace_at(
root: &Path,
cargo_config: &CargoConfig,
load_config: &LoadCargoConfig,
progress: &dyn Fn(String),
) -> Result<(AnalysisHost, vfs::Vfs, Option<ProcMacroServer>)> {
let root = AbsPathBuf::assert(std::env::current_dir()?.join(root));
let root = ProjectManifest::discover_single(&root)?;
let mut workspace = ProjectWorkspace::load(root, cargo_config, progress)?;
if load_config.load_out_dirs_from_check {
let build_scripts = workspace.run_build_scripts(cargo_config, progress)?;
workspace.set_build_scripts(build_scripts)
}
load_workspace(workspace, &cargo_config.extra_env, load_config)
}
// Note: Since this function is used by external tools that use rust-analyzer as a library
// what otherwise would be `pub(crate)` has to be `pub` here instead.
//
// The reason both, `load_workspace_at` and `load_workspace` are `pub` is that some of
// these tools need access to `ProjectWorkspace`, too, which `load_workspace_at` hides.
pub fn load_workspace(
ws: ProjectWorkspace,
extra_env: &FxHashMap<String, String>,
load_config: &LoadCargoConfig,
) -> Result<(AnalysisHost, vfs::Vfs, Option<ProcMacroServer>)> {
let (sender, receiver) = unbounded();
let mut vfs = vfs::Vfs::default();
let mut loader = {
let loader =
vfs_notify::NotifyHandle::spawn(Box::new(move |msg| sender.send(msg).unwrap()));
Box::new(loader)
};
let proc_macro_server = match &load_config.with_proc_macro_server {
ProcMacroServerChoice::Sysroot => ws
.find_sysroot_proc_macro_srv()
.and_then(|it| ProcMacroServer::spawn(it).map_err(Into::into)),
ProcMacroServerChoice::Explicit(path) => {
ProcMacroServer::spawn(path.clone()).map_err(Into::into)
}
ProcMacroServerChoice::None => Err(anyhow!("proc macro server disabled")),
};
let (crate_graph, proc_macros) = ws.to_crate_graph(
&mut |path: &AbsPath| {
let contents = loader.load_sync(path);
let path = vfs::VfsPath::from(path.to_path_buf());
vfs.set_file_contents(path.clone(), contents);
vfs.file_id(&path)
},
extra_env,
);
let proc_macros = {
let proc_macro_server = match &proc_macro_server {
Ok(it) => Ok(it),
Err(e) => Err(e.to_string()),
};
proc_macros
.into_iter()
.map(|(crate_id, path)| {
(
crate_id,
path.map_or_else(
|_| Err("proc macro crate is missing dylib".to_owned()),
|(_, path)| {
proc_macro_server.as_ref().map_err(Clone::clone).and_then(
|proc_macro_server| load_proc_macro(proc_macro_server, &path, &[]),
)
},
),
)
})
.collect()
};
let project_folders = ProjectFolders::new(&[ws], &[]);
loader.set_config(vfs::loader::Config {
load: project_folders.load,
watch: vec![],
version: 0,
});
tracing::debug!("crate graph: {:?}", crate_graph);
let host = load_crate_graph(
crate_graph,
proc_macros,
project_folders.source_root_config,
&mut vfs,
&receiver,
);
if load_config.prefill_caches {
host.analysis().parallel_prime_caches(1, |_| {})?;
}
Ok((host, vfs, proc_macro_server.ok()))
}
fn load_crate_graph(
crate_graph: CrateGraph,
proc_macros: ProcMacros,
source_root_config: SourceRootConfig,
vfs: &mut vfs::Vfs,
receiver: &Receiver<vfs::loader::Message>,
) -> AnalysisHost {
let lru_cap = std::env::var("RA_LRU_CAP").ok().and_then(|it| it.parse::<usize>().ok());
let mut host = AnalysisHost::new(lru_cap);
let mut analysis_change = Change::new();
host.raw_database_mut().enable_proc_attr_macros();
// wait until Vfs has loaded all roots
for task in receiver {
match task {
vfs::loader::Message::Progress { n_done, n_total, config_version: _ } => {
if n_done == n_total {
break;
}
}
vfs::loader::Message::Loaded { files } => {
for (path, contents) in files {
vfs.set_file_contents(path.into(), contents);
}
}
}
}
let changes = vfs.take_changes();
for file in changes {
if file.exists() {
let contents = vfs.file_contents(file.file_id);
if let Ok(text) = std::str::from_utf8(contents) {
analysis_change.change_file(file.file_id, Some(Arc::from(text)))
}
}
}
let source_roots = source_root_config.partition(vfs);
analysis_change.set_roots(source_roots);
analysis_change.set_crate_graph(crate_graph);
analysis_change.set_proc_macros(proc_macros);
host.apply_change(analysis_change);
host
}
#[cfg(test)]
mod tests {
use super::*;
use hir::Crate;
#[test]
fn test_loading_rust_analyzer() {
let path = Path::new(env!("CARGO_MANIFEST_DIR")).parent().unwrap().parent().unwrap();
let cargo_config = CargoConfig::default();
let load_cargo_config = LoadCargoConfig {
load_out_dirs_from_check: false,
with_proc_macro_server: ProcMacroServerChoice::None,
prefill_caches: false,
};
let (host, _vfs, _proc_macro) =
load_workspace_at(path, &cargo_config, &load_cargo_config, &|_| {}).unwrap();
let n_crates = Crate::all(host.raw_database()).len();
// RA has quite a few crates, but the exact count doesn't matter
assert!(n_crates > 20);
}
}

View file

@ -8,23 +8,22 @@ use ide::{
Analysis, FileId, FileRange, MonikerKind, PackageInformation, RootDatabase, StaticIndex,
StaticIndexedFile, TokenId, TokenStaticData,
};
use ide_db::LineIndexDatabase;
use ide_db::base_db::salsa::{self, ParallelDatabase};
use ide_db::line_index::WideEncoding;
use ide_db::{
base_db::salsa::{self, ParallelDatabase},
line_index::WideEncoding,
LineIndexDatabase,
};
use load_cargo::{load_workspace, LoadCargoConfig, ProcMacroServerChoice};
use lsp_types::{self, lsif};
use project_model::{CargoConfig, ProjectManifest, ProjectWorkspace, RustLibSource};
use vfs::{AbsPathBuf, Vfs};
use crate::cli::load_cargo::ProcMacroServerChoice;
use crate::cli::{
flags,
load_cargo::{load_workspace, LoadCargoConfig},
Result,
use crate::{
cli::flags,
line_index::{LineEndings, LineIndex, PositionEncoding},
to_proto,
version::version,
};
use crate::line_index::{LineEndings, LineIndex, PositionEncoding};
use crate::to_proto;
use crate::version::version;
/// Need to wrap Snapshot to provide `Clone` impl for `map_with`
struct Snap<DB>(DB);
@ -49,8 +48,8 @@ struct LsifManager<'a> {
struct Id(i32);
impl From<Id> for lsp_types::NumberOrString {
fn from(Id(x): Id) -> Self {
lsp_types::NumberOrString::Number(x)
fn from(Id(it): Id) -> Self {
lsp_types::NumberOrString::Number(it)
}
}
@ -89,8 +88,8 @@ impl LsifManager<'_> {
}
fn get_token_id(&mut self, id: TokenId) -> Id {
if let Some(x) = self.token_map.get(&id) {
return *x;
if let Some(it) = self.token_map.get(&id) {
return *it;
}
let result_set_id = self.add_vertex(lsif::Vertex::ResultSet(lsif::ResultSet { key: None }));
self.token_map.insert(id, result_set_id);
@ -98,8 +97,8 @@ impl LsifManager<'_> {
}
fn get_package_id(&mut self, package_information: PackageInformation) -> Id {
if let Some(x) = self.package_map.get(&package_information) {
return *x;
if let Some(it) = self.package_map.get(&package_information) {
return *it;
}
let pi = package_information.clone();
let result_set_id =
@ -120,8 +119,8 @@ impl LsifManager<'_> {
}
fn get_range_id(&mut self, id: FileRange) -> Id {
if let Some(x) = self.range_map.get(&id) {
return *x;
if let Some(it) = self.range_map.get(&id) {
return *it;
}
let file_id = id.file_id;
let doc_id = self.get_file_id(file_id);
@ -143,8 +142,8 @@ impl LsifManager<'_> {
}
fn get_file_id(&mut self, id: FileId) -> Id {
if let Some(x) = self.file_map.get(&id) {
return *x;
if let Some(it) = self.file_map.get(&id) {
return *it;
}
let path = self.vfs.file_path(id);
let path = path.as_path().unwrap();
@ -217,18 +216,18 @@ impl LsifManager<'_> {
}));
let mut edges = token.references.iter().fold(
HashMap::<_, Vec<lsp_types::NumberOrString>>::new(),
|mut edges, x| {
|mut edges, it| {
let entry =
edges.entry((x.range.file_id, x.is_definition)).or_insert_with(Vec::new);
entry.push((*self.range_map.get(&x.range).unwrap()).into());
edges.entry((it.range.file_id, it.is_definition)).or_insert_with(Vec::new);
entry.push((*self.range_map.get(&it.range).unwrap()).into());
edges
},
);
for x in token.references {
if let Some(vertices) = edges.remove(&(x.range.file_id, x.is_definition)) {
for it in token.references {
if let Some(vertices) = edges.remove(&(it.range.file_id, it.is_definition)) {
self.add_edge(lsif::Edge::Item(lsif::Item {
document: (*self.file_map.get(&x.range.file_id).unwrap()).into(),
property: Some(if x.is_definition {
document: (*self.file_map.get(&it.range.file_id).unwrap()).into(),
property: Some(if it.is_definition {
lsif::ItemKind::Definitions
} else {
lsif::ItemKind::References
@ -286,7 +285,7 @@ impl LsifManager<'_> {
}
impl flags::Lsif {
pub fn run(self) -> Result<()> {
pub fn run(self) -> anyhow::Result<()> {
eprintln!("Generating LSIF started...");
let now = Instant::now();
let mut cargo_config = CargoConfig::default();

View file

@ -0,0 +1,89 @@
//! Run all tests in a project, similar to `cargo test`, but using the mir interpreter.
use hir::{Crate, Module};
use hir_ty::db::HirDatabase;
use ide_db::{base_db::SourceDatabaseExt, LineIndexDatabase};
use profile::StopWatch;
use project_model::{CargoConfig, RustLibSource};
use syntax::TextRange;
use load_cargo::{load_workspace_at, LoadCargoConfig, ProcMacroServerChoice};
use crate::cli::{flags, full_name_of_item, Result};
impl flags::RunTests {
pub fn run(self) -> Result<()> {
let mut cargo_config = CargoConfig::default();
cargo_config.sysroot = Some(RustLibSource::Discover);
let load_cargo_config = LoadCargoConfig {
load_out_dirs_from_check: true,
with_proc_macro_server: ProcMacroServerChoice::Sysroot,
prefill_caches: false,
};
let (host, _vfs, _proc_macro) =
load_workspace_at(&self.path, &cargo_config, &load_cargo_config, &|_| {})?;
let db = host.raw_database();
let tests = all_modules(db)
.into_iter()
.flat_map(|x| x.declarations(db))
.filter_map(|x| match x {
hir::ModuleDef::Function(f) => Some(f),
_ => None,
})
.filter(|x| x.is_test(db));
let span_formatter = |file_id, text_range: TextRange| {
let line_col = match db.line_index(file_id).try_line_col(text_range.start()) {
None => " (unknown line col)".to_string(),
Some(x) => format!("#{}:{}", x.line + 1, x.col),
};
let path = &db
.source_root(db.file_source_root(file_id))
.path_for_file(&file_id)
.map(|x| x.to_string());
let path = path.as_deref().unwrap_or("<unknown file>");
format!("file://{path}{line_col}")
};
let mut pass_count = 0;
let mut ignore_count = 0;
let mut fail_count = 0;
let mut sw_all = StopWatch::start();
for test in tests {
let full_name = full_name_of_item(db, test.module(db), test.name(db));
println!("test {}", full_name);
if test.is_ignore(db) {
println!("ignored");
ignore_count += 1;
continue;
}
let mut sw_one = StopWatch::start();
let result = test.eval(db, span_formatter);
if result.trim() == "pass" {
pass_count += 1;
} else {
fail_count += 1;
}
println!("{}", result);
eprintln!("{:<20} {}", format!("test {}", full_name), sw_one.elapsed());
}
println!("{pass_count} passed, {fail_count} failed, {ignore_count} ignored");
eprintln!("{:<20} {}", "All tests", sw_all.elapsed());
Ok(())
}
}
fn all_modules(db: &dyn HirDatabase) -> Vec<Module> {
let mut worklist: Vec<_> = Crate::all(db)
.into_iter()
.filter(|x| x.origin(db).is_local())
.map(|krate| krate.root_module(db))
.collect();
let mut modules = Vec::new();
while let Some(module) = worklist.pop() {
modules.push(module);
worklist.extend(module.children(db));
}
modules
}

View file

@ -6,27 +6,23 @@ use std::{
time::Instant,
};
use crate::{
cli::load_cargo::ProcMacroServerChoice,
line_index::{LineEndings, LineIndex, PositionEncoding},
};
use ide::{
LineCol, MonikerDescriptorKind, StaticIndex, StaticIndexedFile, TextRange, TokenId,
TokenStaticData,
};
use ide_db::LineIndexDatabase;
use load_cargo::{load_workspace, LoadCargoConfig, ProcMacroServerChoice};
use project_model::{CargoConfig, ProjectManifest, ProjectWorkspace, RustLibSource};
use scip::types as scip_types;
use std::env;
use crate::cli::{
flags,
load_cargo::{load_workspace, LoadCargoConfig},
Result,
use crate::{
cli::flags,
line_index::{LineEndings, LineIndex, PositionEncoding},
};
impl flags::Scip {
pub fn run(self) -> Result<()> {
pub fn run(self) -> anyhow::Result<()> {
eprintln!("Generating SCIP start...");
let now = Instant::now();
let mut cargo_config = CargoConfig::default();
@ -65,7 +61,7 @@ impl flags::Scip {
path.normalize()
.as_os_str()
.to_str()
.ok_or(anyhow::anyhow!("Unable to normalize project_root path"))?
.ok_or(anyhow::format_err!("Unable to normalize project_root path"))?
),
text_document_encoding: scip_types::TextEncoding::UTF8.into(),
special_fields: Default::default(),
@ -168,7 +164,7 @@ impl flags::Scip {
let out_path = self.output.unwrap_or_else(|| PathBuf::from(r"index.scip"));
scip::write_message_to_file(out_path, index)
.map_err(|err| anyhow::anyhow!("Failed to write scip to file: {}", err))?;
.map_err(|err| anyhow::format_err!("Failed to write scip to file: {}", err))?;
eprintln!("Generating SCIP finished {:?}", now.elapsed());
Ok(())
@ -276,7 +272,7 @@ mod test {
let change_fixture = ChangeFixture::parse(ra_fixture);
host.raw_database_mut().apply_change(change_fixture.change);
let (file_id, range_or_offset) =
change_fixture.file_position.expect("expected a marker ($0)");
change_fixture.file_position.expect("expected a marker ()");
let offset = range_or_offset.expect_offset();
(host, FilePosition { file_id, offset })
}
@ -325,7 +321,7 @@ use foo::example_mod::func;
fn main() {
func$0();
}
//- /foo/lib.rs crate:foo@CratesIo:0.1.0,https://a.b/foo.git
//- /foo/lib.rs crate:foo@0.1.0,https://a.b/foo.git library
pub mod example_mod {
pub fn func() {}
}
@ -338,7 +334,7 @@ pub mod example_mod {
fn symbol_for_trait() {
check_symbol(
r#"
//- /foo/lib.rs crate:foo@CratesIo:0.1.0,https://a.b/foo.git
//- /foo/lib.rs crate:foo@0.1.0,https://a.b/foo.git library
pub mod module {
pub trait MyTrait {
pub fn func$0() {}
@ -353,7 +349,7 @@ pub mod module {
fn symbol_for_trait_constant() {
check_symbol(
r#"
//- /foo/lib.rs crate:foo@CratesIo:0.1.0,https://a.b/foo.git
//- /foo/lib.rs crate:foo@0.1.0,https://a.b/foo.git library
pub mod module {
pub trait MyTrait {
const MY_CONST$0: u8;
@ -368,7 +364,7 @@ pub mod module {
fn symbol_for_trait_type() {
check_symbol(
r#"
//- /foo/lib.rs crate:foo@CratesIo:0.1.0,https://a.b/foo.git
//- /foo/lib.rs crate:foo@0.1.0,https://a.b/foo.git library
pub mod module {
pub trait MyTrait {
type MyType$0;
@ -384,7 +380,7 @@ pub mod module {
fn symbol_for_trait_impl_function() {
check_symbol(
r#"
//- /foo/lib.rs crate:foo@CratesIo:0.1.0,https://a.b/foo.git
//- /foo/lib.rs crate:foo@0.1.0,https://a.b/foo.git library
pub mod module {
pub trait MyTrait {
pub fn func() {}
@ -411,7 +407,7 @@ pub mod module {
fn main() {
let x = St { a$0: 2 };
}
//- /foo/lib.rs crate:foo@CratesIo:0.1.0,https://a.b/foo.git
//- /foo/lib.rs crate:foo@0.1.0,https://a.b/foo.git library
pub struct St {
pub a: i32,
}
@ -429,7 +425,7 @@ pub mod module {
fn main() {
func();
}
//- /foo/lib.rs crate:foo@CratesIo:0.1.0,https://a.b/foo.git
//- /foo/lib.rs crate:foo@0.1.0,https://a.b/foo.git library
pub mod module {
pub fn func() {
let x$0 = 2;

View file

@ -1,16 +1,14 @@
//! Applies structured search replace rules from the command line.
use anyhow::Context;
use ide_ssr::MatchFinder;
use load_cargo::{load_workspace_at, LoadCargoConfig, ProcMacroServerChoice};
use project_model::{CargoConfig, RustLibSource};
use crate::cli::{
flags,
load_cargo::{load_workspace_at, LoadCargoConfig, ProcMacroServerChoice},
Result,
};
use crate::cli::flags;
impl flags::Ssr {
pub fn run(self) -> Result<()> {
pub fn run(self) -> anyhow::Result<()> {
use ide_db::base_db::SourceDatabaseExt;
let mut cargo_config = CargoConfig::default();
cargo_config.sysroot = Some(RustLibSource::Discover);
@ -35,7 +33,8 @@ impl flags::Ssr {
if let Some(path) = vfs.file_path(file_id).as_path() {
let mut contents = db.file_text(file_id).to_string();
edit.apply(&mut contents);
std::fs::write(path, contents)?;
std::fs::write(path, contents)
.with_context(|| format!("failed to write {path}"))?;
}
}
Ok(())
@ -46,7 +45,7 @@ impl flags::Search {
/// Searches for `patterns`, printing debug information for any nodes whose text exactly matches
/// `debug_snippet`. This is intended for debugging and probably isn't in it's current form useful
/// for much else.
pub fn run(self) -> Result<()> {
pub fn run(self) -> anyhow::Result<()> {
use ide_db::base_db::SourceDatabaseExt;
use ide_db::symbol_index::SymbolsDatabase;
let cargo_config = CargoConfig::default();

View file

@ -1079,6 +1079,7 @@ impl Config {
pub fn diagnostics(&self) -> DiagnosticsConfig {
DiagnosticsConfig {
enabled: self.data.diagnostics_enable,
proc_attr_macros_enabled: self.expand_proc_attr_macros(),
proc_macros_enabled: self.data.procMacro_enable,
disable_experimental: !self.data.diagnostics_experimental_enable,

View file

@ -10,7 +10,7 @@ use crate::{
global_state::{GlobalState, GlobalStateSnapshot},
main_loop::Task,
version::version,
LspError, Result,
LspError,
};
/// A visitor for routing a raw JSON request to an appropriate handler function.
@ -32,13 +32,13 @@ pub(crate) struct RequestDispatcher<'a> {
pub(crate) global_state: &'a mut GlobalState,
}
impl<'a> RequestDispatcher<'a> {
impl RequestDispatcher<'_> {
/// Dispatches the request onto the current thread, given full access to
/// mutable global state. Unlike all other methods here, this one isn't
/// guarded by `catch_unwind`, so, please, don't make bugs :-)
pub(crate) fn on_sync_mut<R>(
&mut self,
f: fn(&mut GlobalState, R::Params) -> Result<R::Result>,
f: fn(&mut GlobalState, R::Params) -> anyhow::Result<R::Result>,
) -> &mut Self
where
R: lsp_types::request::Request,
@ -63,7 +63,7 @@ impl<'a> RequestDispatcher<'a> {
/// Dispatches the request onto the current thread.
pub(crate) fn on_sync<R>(
&mut self,
f: fn(GlobalStateSnapshot, R::Params) -> Result<R::Result>,
f: fn(GlobalStateSnapshot, R::Params) -> anyhow::Result<R::Result>,
) -> &mut Self
where
R: lsp_types::request::Request,
@ -92,7 +92,7 @@ impl<'a> RequestDispatcher<'a> {
/// without retrying it if it panics.
pub(crate) fn on_no_retry<R>(
&mut self,
f: fn(GlobalStateSnapshot, R::Params) -> Result<R::Result>,
f: fn(GlobalStateSnapshot, R::Params) -> anyhow::Result<R::Result>,
) -> &mut Self
where
R: lsp_types::request::Request + 'static,
@ -128,7 +128,7 @@ impl<'a> RequestDispatcher<'a> {
/// Dispatches a non-latency-sensitive request onto the thread pool.
pub(crate) fn on<R>(
&mut self,
f: fn(GlobalStateSnapshot, R::Params) -> Result<R::Result>,
f: fn(GlobalStateSnapshot, R::Params) -> anyhow::Result<R::Result>,
) -> &mut Self
where
R: lsp_types::request::Request + 'static,
@ -141,7 +141,7 @@ impl<'a> RequestDispatcher<'a> {
/// Dispatches a latency-sensitive request onto the thread pool.
pub(crate) fn on_latency_sensitive<R>(
&mut self,
f: fn(GlobalStateSnapshot, R::Params) -> Result<R::Result>,
f: fn(GlobalStateSnapshot, R::Params) -> anyhow::Result<R::Result>,
) -> &mut Self
where
R: lsp_types::request::Request + 'static,
@ -156,7 +156,7 @@ impl<'a> RequestDispatcher<'a> {
/// We can't run this on the main thread though as we invoke rustfmt which may take arbitrary time to complete!
pub(crate) fn on_fmt_thread<R>(
&mut self,
f: fn(GlobalStateSnapshot, R::Params) -> Result<R::Result>,
f: fn(GlobalStateSnapshot, R::Params) -> anyhow::Result<R::Result>,
) -> &mut Self
where
R: lsp_types::request::Request + 'static,
@ -181,7 +181,7 @@ impl<'a> RequestDispatcher<'a> {
fn on_with_thread_intent<const MAIN_POOL: bool, R>(
&mut self,
intent: ThreadIntent,
f: fn(GlobalStateSnapshot, R::Params) -> Result<R::Result>,
f: fn(GlobalStateSnapshot, R::Params) -> anyhow::Result<R::Result>,
) -> &mut Self
where
R: lsp_types::request::Request + 'static,
@ -245,7 +245,7 @@ impl<'a> RequestDispatcher<'a> {
fn thread_result_to_response<R>(
id: lsp_server::RequestId,
result: thread::Result<Result<R::Result>>,
result: thread::Result<anyhow::Result<R::Result>>,
) -> Result<lsp_server::Response, Cancelled>
where
R: lsp_types::request::Request,
@ -277,7 +277,7 @@ where
fn result_to_response<R>(
id: lsp_server::RequestId,
result: Result<R::Result>,
result: anyhow::Result<R::Result>,
) -> Result<lsp_server::Response, Cancelled>
where
R: lsp_types::request::Request,
@ -289,7 +289,7 @@ where
Err(e) => match e.downcast::<LspError>() {
Ok(lsp_error) => lsp_server::Response::new_err(id, lsp_error.code, lsp_error.message),
Err(e) => match e.downcast::<Cancelled>() {
Ok(cancelled) => return Err(*cancelled),
Ok(cancelled) => return Err(cancelled),
Err(e) => lsp_server::Response::new_err(
id,
lsp_server::ErrorCode::InternalError as i32,
@ -306,11 +306,11 @@ pub(crate) struct NotificationDispatcher<'a> {
pub(crate) global_state: &'a mut GlobalState,
}
impl<'a> NotificationDispatcher<'a> {
impl NotificationDispatcher<'_> {
pub(crate) fn on_sync_mut<N>(
&mut self,
f: fn(&mut GlobalState, N::Params) -> Result<()>,
) -> Result<&mut Self>
f: fn(&mut GlobalState, N::Params) -> anyhow::Result<()>,
) -> anyhow::Result<&mut Self>
where
N: lsp_types::notification::Notification,
N::Params: DeserializeOwned + Send,

View file

@ -14,19 +14,21 @@ use crate::{
line_index::{LineIndex, PositionEncoding},
lsp_ext,
lsp_utils::invalid_params_error,
Result,
};
pub(crate) fn abs_path(url: &lsp_types::Url) -> Result<AbsPathBuf> {
let path = url.to_file_path().map_err(|()| "url is not a file")?;
pub(crate) fn abs_path(url: &lsp_types::Url) -> anyhow::Result<AbsPathBuf> {
let path = url.to_file_path().map_err(|()| anyhow::format_err!("url is not a file"))?;
Ok(AbsPathBuf::try_from(path).unwrap())
}
pub(crate) fn vfs_path(url: &lsp_types::Url) -> Result<vfs::VfsPath> {
pub(crate) fn vfs_path(url: &lsp_types::Url) -> anyhow::Result<vfs::VfsPath> {
abs_path(url).map(vfs::VfsPath::from)
}
pub(crate) fn offset(line_index: &LineIndex, position: lsp_types::Position) -> Result<TextSize> {
pub(crate) fn offset(
line_index: &LineIndex,
position: lsp_types::Position,
) -> anyhow::Result<TextSize> {
let line_col = match line_index.encoding {
PositionEncoding::Utf8 => LineCol { line: position.line, col: position.character },
PositionEncoding::Wide(enc) => {
@ -42,7 +44,10 @@ pub(crate) fn offset(line_index: &LineIndex, position: lsp_types::Position) -> R
Ok(text_size)
}
pub(crate) fn text_range(line_index: &LineIndex, range: lsp_types::Range) -> Result<TextRange> {
pub(crate) fn text_range(
line_index: &LineIndex,
range: lsp_types::Range,
) -> anyhow::Result<TextRange> {
let start = offset(line_index, range.start)?;
let end = offset(line_index, range.end)?;
match end < start {
@ -51,14 +56,14 @@ pub(crate) fn text_range(line_index: &LineIndex, range: lsp_types::Range) -> Res
}
}
pub(crate) fn file_id(snap: &GlobalStateSnapshot, url: &lsp_types::Url) -> Result<FileId> {
pub(crate) fn file_id(snap: &GlobalStateSnapshot, url: &lsp_types::Url) -> anyhow::Result<FileId> {
snap.url_to_file_id(url)
}
pub(crate) fn file_position(
snap: &GlobalStateSnapshot,
tdpp: lsp_types::TextDocumentPositionParams,
) -> Result<FilePosition> {
) -> anyhow::Result<FilePosition> {
let file_id = file_id(snap, &tdpp.text_document.uri)?;
let line_index = snap.file_line_index(file_id)?;
let offset = offset(&line_index, tdpp.position)?;
@ -69,7 +74,7 @@ pub(crate) fn file_range(
snap: &GlobalStateSnapshot,
text_document_identifier: lsp_types::TextDocumentIdentifier,
range: lsp_types::Range,
) -> Result<FileRange> {
) -> anyhow::Result<FileRange> {
file_range_uri(snap, &text_document_identifier.uri, range)
}
@ -77,7 +82,7 @@ pub(crate) fn file_range_uri(
snap: &GlobalStateSnapshot,
document: &lsp_types::Url,
range: lsp_types::Range,
) -> Result<FileRange> {
) -> anyhow::Result<FileRange> {
let file_id = file_id(snap, document)?;
let line_index = snap.file_line_index(file_id)?;
let range = text_range(&line_index, range)?;
@ -101,7 +106,7 @@ pub(crate) fn assist_kind(kind: lsp_types::CodeActionKind) -> Option<AssistKind>
pub(crate) fn annotation(
snap: &GlobalStateSnapshot,
code_lens: lsp_types::CodeLens,
) -> Result<Option<Annotation>> {
) -> anyhow::Result<Option<Annotation>> {
let data =
code_lens.data.ok_or_else(|| invalid_params_error("code lens without data".to_string()))?;
let resolve = from_json::<lsp_ext::CodeLensResolveData>("CodeLensResolveData", &data)?;

View file

@ -9,6 +9,7 @@ use crossbeam_channel::{unbounded, Receiver, Sender};
use flycheck::FlycheckHandle;
use ide::{Analysis, AnalysisHost, Cancellable, Change, FileId};
use ide_db::base_db::{CrateId, FileLoader, ProcMacroPaths, SourceDatabase};
use load_cargo::SourceRootConfig;
use lsp_types::{SemanticTokens, Url};
use nohash_hasher::IntMap;
use parking_lot::{Mutex, RwLock};
@ -27,10 +28,9 @@ use crate::{
main_loop::Task,
mem_docs::MemDocs,
op_queue::OpQueue,
reload::{self, SourceRootConfig},
reload,
task_pool::TaskPool,
to_proto::url_from_abs_path,
Result,
};
// Enforces drop order
@ -319,7 +319,7 @@ impl GlobalState {
// crate see https://github.com/rust-lang/rust-analyzer/issues/13029
if let Some((path, force_crate_graph_reload)) = workspace_structure_change {
self.fetch_workspaces_queue.request_op(
format!("workspace vfs file change: {}", path.display()),
format!("workspace vfs file change: {path}"),
force_crate_graph_reload,
);
}
@ -422,7 +422,7 @@ impl Drop for GlobalState {
}
impl GlobalStateSnapshot {
pub(crate) fn url_to_file_id(&self, url: &Url) -> Result<FileId> {
pub(crate) fn url_to_file_id(&self, url: &Url) -> anyhow::Result<FileId> {
url_to_file_id(&self.vfs.read().0, url)
}
@ -481,8 +481,8 @@ pub(crate) fn file_id_to_url(vfs: &vfs::Vfs, id: FileId) -> Url {
url_from_abs_path(path)
}
pub(crate) fn url_to_file_id(vfs: &vfs::Vfs, url: &Url) -> Result<FileId> {
pub(crate) fn url_to_file_id(vfs: &vfs::Vfs, url: &Url) -> anyhow::Result<FileId> {
let path = from_proto::vfs_path(url)?;
let res = vfs.file_id(&path).ok_or_else(|| format!("file not found: {path}"))?;
let res = vfs.file_id(&path).ok_or_else(|| anyhow::format_err!("file not found: {path}"))?;
Ok(res)
}

View file

@ -14,10 +14,10 @@ use vfs::{AbsPathBuf, ChangeKind, VfsPath};
use crate::{
config::Config, from_proto, global_state::GlobalState, lsp_ext::RunFlycheckParams,
lsp_utils::apply_document_changes, mem_docs::DocumentData, reload, Result,
lsp_utils::apply_document_changes, mem_docs::DocumentData, reload,
};
pub(crate) fn handle_cancel(state: &mut GlobalState, params: CancelParams) -> Result<()> {
pub(crate) fn handle_cancel(state: &mut GlobalState, params: CancelParams) -> anyhow::Result<()> {
let id: lsp_server::RequestId = match params.id {
lsp_types::NumberOrString::Number(id) => id.into(),
lsp_types::NumberOrString::String(id) => id.into(),
@ -29,7 +29,7 @@ pub(crate) fn handle_cancel(state: &mut GlobalState, params: CancelParams) -> Re
pub(crate) fn handle_work_done_progress_cancel(
state: &mut GlobalState,
params: WorkDoneProgressCancelParams,
) -> Result<()> {
) -> anyhow::Result<()> {
if let lsp_types::NumberOrString::String(s) = &params.token {
if let Some(id) = s.strip_prefix("rust-analyzer/flycheck/") {
if let Ok(id) = u32::from_str_radix(id, 10) {
@ -49,7 +49,7 @@ pub(crate) fn handle_work_done_progress_cancel(
pub(crate) fn handle_did_open_text_document(
state: &mut GlobalState,
params: DidOpenTextDocumentParams,
) -> Result<()> {
) -> anyhow::Result<()> {
let _p = profile::span("handle_did_open_text_document");
if let Ok(path) = from_proto::vfs_path(&params.text_document.uri) {
@ -68,7 +68,7 @@ pub(crate) fn handle_did_open_text_document(
pub(crate) fn handle_did_change_text_document(
state: &mut GlobalState,
params: DidChangeTextDocumentParams,
) -> Result<()> {
) -> anyhow::Result<()> {
let _p = profile::span("handle_did_change_text_document");
if let Ok(path) = from_proto::vfs_path(&params.text_document.uri) {
@ -100,7 +100,7 @@ pub(crate) fn handle_did_change_text_document(
pub(crate) fn handle_did_close_text_document(
state: &mut GlobalState,
params: DidCloseTextDocumentParams,
) -> Result<()> {
) -> anyhow::Result<()> {
let _p = profile::span("handle_did_close_text_document");
if let Ok(path) = from_proto::vfs_path(&params.text_document.uri) {
@ -120,14 +120,14 @@ pub(crate) fn handle_did_close_text_document(
pub(crate) fn handle_did_save_text_document(
state: &mut GlobalState,
params: DidSaveTextDocumentParams,
) -> Result<()> {
) -> anyhow::Result<()> {
if let Ok(vfs_path) = from_proto::vfs_path(&params.text_document.uri) {
// Re-fetch workspaces if a workspace related file has changed
if let Some(abs_path) = vfs_path.as_path() {
if reload::should_refresh_for_change(abs_path, ChangeKind::Modify) {
state
.fetch_workspaces_queue
.request_op(format!("DidSaveTextDocument {}", abs_path.display()), false);
.request_op(format!("DidSaveTextDocument {abs_path}"), false);
}
}
@ -146,7 +146,7 @@ pub(crate) fn handle_did_save_text_document(
pub(crate) fn handle_did_change_configuration(
state: &mut GlobalState,
_params: DidChangeConfigurationParams,
) -> Result<()> {
) -> anyhow::Result<()> {
// As stated in https://github.com/microsoft/language-server-protocol/issues/676,
// this notification's parameters should be ignored and the actual config queried separately.
state.send_request::<lsp_types::request::WorkspaceConfiguration>(
@ -186,7 +186,7 @@ pub(crate) fn handle_did_change_configuration(
pub(crate) fn handle_did_change_workspace_folders(
state: &mut GlobalState,
params: DidChangeWorkspaceFoldersParams,
) -> Result<()> {
) -> anyhow::Result<()> {
let config = Arc::make_mut(&mut state.config);
for workspace in params.event.removed {
@ -214,7 +214,7 @@ pub(crate) fn handle_did_change_workspace_folders(
pub(crate) fn handle_did_change_watched_files(
state: &mut GlobalState,
params: DidChangeWatchedFilesParams,
) -> Result<()> {
) -> anyhow::Result<()> {
for change in params.changes {
if let Ok(path) = from_proto::abs_path(&change.uri) {
state.loader.handle.invalidate(path);
@ -302,13 +302,13 @@ fn run_flycheck(state: &mut GlobalState, vfs_path: VfsPath) -> bool {
}
}
pub(crate) fn handle_cancel_flycheck(state: &mut GlobalState, _: ()) -> Result<()> {
pub(crate) fn handle_cancel_flycheck(state: &mut GlobalState, _: ()) -> anyhow::Result<()> {
let _p = profile::span("handle_stop_flycheck");
state.flycheck.iter().for_each(|flycheck| flycheck.cancel());
Ok(())
}
pub(crate) fn handle_clear_flycheck(state: &mut GlobalState, _: ()) -> Result<()> {
pub(crate) fn handle_clear_flycheck(state: &mut GlobalState, _: ()) -> anyhow::Result<()> {
let _p = profile::span("handle_clear_flycheck");
state.diagnostics.clear_check_all();
Ok(())
@ -317,7 +317,7 @@ pub(crate) fn handle_clear_flycheck(state: &mut GlobalState, _: ()) -> Result<()
pub(crate) fn handle_run_flycheck(
state: &mut GlobalState,
params: RunFlycheckParams,
) -> Result<()> {
) -> anyhow::Result<()> {
let _p = profile::span("handle_run_flycheck");
if let Some(text_document) = params.text_document {
if let Ok(vfs_path) = from_proto::vfs_path(&text_document.uri) {

View file

@ -8,6 +8,7 @@ use std::{
};
use anyhow::Context;
use ide::{
AnnotationConfig, AssistKind, AssistResolveStrategy, Cancellable, FilePosition, FileRange,
HoverAction, HoverGotoTypeData, Query, RangeInfo, ReferenceCategory, Runnable, RunnableKind,
@ -20,9 +21,9 @@ use lsp_types::{
CallHierarchyOutgoingCall, CallHierarchyOutgoingCallsParams, CallHierarchyPrepareParams,
CodeLens, CompletionItem, FoldingRange, FoldingRangeParams, HoverContents, InlayHint,
InlayHintParams, Location, LocationLink, Position, PrepareRenameResponse, Range, RenameParams,
SemanticTokensDeltaParams, SemanticTokensFullDeltaResult, SemanticTokensParams,
SemanticTokensRangeParams, SemanticTokensRangeResult, SemanticTokensResult, SymbolInformation,
SymbolTag, TextDocumentIdentifier, Url, WorkspaceEdit,
ResourceOp, ResourceOperationKind, SemanticTokensDeltaParams, SemanticTokensFullDeltaResult,
SemanticTokensParams, SemanticTokensRangeParams, SemanticTokensRangeResult,
SemanticTokensResult, SymbolInformation, SymbolTag, TextDocumentIdentifier, Url, WorkspaceEdit,
};
use project_model::{ManifestPath, ProjectWorkspace, TargetKind};
use serde_json::json;
@ -33,7 +34,7 @@ use vfs::{AbsPath, AbsPathBuf, VfsPath};
use crate::{
cargo_target_spec::CargoTargetSpec,
config::{RustfmtConfig, WorkspaceSymbolConfig},
config::{Config, RustfmtConfig, WorkspaceSymbolConfig},
diff::diff,
from_proto,
global_state::{GlobalState, GlobalStateSnapshot},
@ -43,10 +44,10 @@ use crate::{
FetchDependencyListResult, PositionOrRange, ViewCrateGraphParams, WorkspaceSymbolParams,
},
lsp_utils::{all_edits_are_disjoint, invalid_params_error},
to_proto, LspError, Result,
to_proto, LspError,
};
pub(crate) fn handle_workspace_reload(state: &mut GlobalState, _: ()) -> Result<()> {
pub(crate) fn handle_workspace_reload(state: &mut GlobalState, _: ()) -> anyhow::Result<()> {
// FIXME: use `Arc::from_iter` when it becomes available
state.proc_macro_clients = Arc::from(Vec::new());
state.proc_macro_changed = false;
@ -55,7 +56,7 @@ pub(crate) fn handle_workspace_reload(state: &mut GlobalState, _: ()) -> Result<
Ok(())
}
pub(crate) fn handle_proc_macros_rebuild(state: &mut GlobalState, _: ()) -> Result<()> {
pub(crate) fn handle_proc_macros_rebuild(state: &mut GlobalState, _: ()) -> anyhow::Result<()> {
// FIXME: use `Arc::from_iter` when it becomes available
state.proc_macro_clients = Arc::from(Vec::new());
state.proc_macro_changed = false;
@ -67,7 +68,7 @@ pub(crate) fn handle_proc_macros_rebuild(state: &mut GlobalState, _: ()) -> Resu
pub(crate) fn handle_analyzer_status(
snap: GlobalStateSnapshot,
params: lsp_ext::AnalyzerStatusParams,
) -> Result<String> {
) -> anyhow::Result<String> {
let _p = profile::span("handle_analyzer_status");
let mut buf = String::new();
@ -112,7 +113,7 @@ pub(crate) fn handle_analyzer_status(
Ok(buf)
}
pub(crate) fn handle_memory_usage(state: &mut GlobalState, _: ()) -> Result<String> {
pub(crate) fn handle_memory_usage(state: &mut GlobalState, _: ()) -> anyhow::Result<String> {
let _p = profile::span("handle_memory_usage");
let mem = state.analysis_host.per_query_memory_usage();
@ -125,7 +126,7 @@ pub(crate) fn handle_memory_usage(state: &mut GlobalState, _: ()) -> Result<Stri
Ok(out)
}
pub(crate) fn handle_shuffle_crate_graph(state: &mut GlobalState, _: ()) -> Result<()> {
pub(crate) fn handle_shuffle_crate_graph(state: &mut GlobalState, _: ()) -> anyhow::Result<()> {
state.analysis_host.shuffle_crate_graph();
Ok(())
}
@ -133,7 +134,7 @@ pub(crate) fn handle_shuffle_crate_graph(state: &mut GlobalState, _: ()) -> Resu
pub(crate) fn handle_syntax_tree(
snap: GlobalStateSnapshot,
params: lsp_ext::SyntaxTreeParams,
) -> Result<String> {
) -> anyhow::Result<String> {
let _p = profile::span("handle_syntax_tree");
let id = from_proto::file_id(&snap, &params.text_document.uri)?;
let line_index = snap.file_line_index(id)?;
@ -145,7 +146,7 @@ pub(crate) fn handle_syntax_tree(
pub(crate) fn handle_view_hir(
snap: GlobalStateSnapshot,
params: lsp_types::TextDocumentPositionParams,
) -> Result<String> {
) -> anyhow::Result<String> {
let _p = profile::span("handle_view_hir");
let position = from_proto::file_position(&snap, params)?;
let res = snap.analysis.view_hir(position)?;
@ -155,7 +156,7 @@ pub(crate) fn handle_view_hir(
pub(crate) fn handle_view_mir(
snap: GlobalStateSnapshot,
params: lsp_types::TextDocumentPositionParams,
) -> Result<String> {
) -> anyhow::Result<String> {
let _p = profile::span("handle_view_mir");
let position = from_proto::file_position(&snap, params)?;
let res = snap.analysis.view_mir(position)?;
@ -165,7 +166,7 @@ pub(crate) fn handle_view_mir(
pub(crate) fn handle_interpret_function(
snap: GlobalStateSnapshot,
params: lsp_types::TextDocumentPositionParams,
) -> Result<String> {
) -> anyhow::Result<String> {
let _p = profile::span("handle_interpret_function");
let position = from_proto::file_position(&snap, params)?;
let res = snap.analysis.interpret_function(position)?;
@ -175,7 +176,7 @@ pub(crate) fn handle_interpret_function(
pub(crate) fn handle_view_file_text(
snap: GlobalStateSnapshot,
params: lsp_types::TextDocumentIdentifier,
) -> Result<String> {
) -> anyhow::Result<String> {
let file_id = from_proto::file_id(&snap, &params.uri)?;
Ok(snap.analysis.file_text(file_id)?.to_string())
}
@ -183,7 +184,7 @@ pub(crate) fn handle_view_file_text(
pub(crate) fn handle_view_item_tree(
snap: GlobalStateSnapshot,
params: lsp_ext::ViewItemTreeParams,
) -> Result<String> {
) -> anyhow::Result<String> {
let _p = profile::span("handle_view_item_tree");
let file_id = from_proto::file_id(&snap, &params.text_document.uri)?;
let res = snap.analysis.view_item_tree(file_id)?;
@ -193,16 +194,16 @@ pub(crate) fn handle_view_item_tree(
pub(crate) fn handle_view_crate_graph(
snap: GlobalStateSnapshot,
params: ViewCrateGraphParams,
) -> Result<String> {
) -> anyhow::Result<String> {
let _p = profile::span("handle_view_crate_graph");
let dot = snap.analysis.view_crate_graph(params.full)??;
let dot = snap.analysis.view_crate_graph(params.full)?.map_err(anyhow::Error::msg)?;
Ok(dot)
}
pub(crate) fn handle_expand_macro(
snap: GlobalStateSnapshot,
params: lsp_ext::ExpandMacroParams,
) -> Result<Option<lsp_ext::ExpandedMacro>> {
) -> anyhow::Result<Option<lsp_ext::ExpandedMacro>> {
let _p = profile::span("handle_expand_macro");
let file_id = from_proto::file_id(&snap, &params.text_document.uri)?;
let line_index = snap.file_line_index(file_id)?;
@ -215,11 +216,11 @@ pub(crate) fn handle_expand_macro(
pub(crate) fn handle_selection_range(
snap: GlobalStateSnapshot,
params: lsp_types::SelectionRangeParams,
) -> Result<Option<Vec<lsp_types::SelectionRange>>> {
) -> anyhow::Result<Option<Vec<lsp_types::SelectionRange>>> {
let _p = profile::span("handle_selection_range");
let file_id = from_proto::file_id(&snap, &params.text_document.uri)?;
let line_index = snap.file_line_index(file_id)?;
let res: Result<Vec<lsp_types::SelectionRange>> = params
let res: anyhow::Result<Vec<lsp_types::SelectionRange>> = params
.positions
.into_iter()
.map(|position| {
@ -258,7 +259,7 @@ pub(crate) fn handle_selection_range(
pub(crate) fn handle_matching_brace(
snap: GlobalStateSnapshot,
params: lsp_ext::MatchingBraceParams,
) -> Result<Vec<Position>> {
) -> anyhow::Result<Vec<Position>> {
let _p = profile::span("handle_matching_brace");
let file_id = from_proto::file_id(&snap, &params.text_document.uri)?;
let line_index = snap.file_line_index(file_id)?;
@ -281,7 +282,7 @@ pub(crate) fn handle_matching_brace(
pub(crate) fn handle_join_lines(
snap: GlobalStateSnapshot,
params: lsp_ext::JoinLinesParams,
) -> Result<Vec<lsp_types::TextEdit>> {
) -> anyhow::Result<Vec<lsp_types::TextEdit>> {
let _p = profile::span("handle_join_lines");
let config = snap.config.join_lines();
@ -306,7 +307,7 @@ pub(crate) fn handle_join_lines(
pub(crate) fn handle_on_enter(
snap: GlobalStateSnapshot,
params: lsp_types::TextDocumentPositionParams,
) -> Result<Option<Vec<lsp_ext::SnippetTextEdit>>> {
) -> anyhow::Result<Option<Vec<lsp_ext::SnippetTextEdit>>> {
let _p = profile::span("handle_on_enter");
let position = from_proto::file_position(&snap, params)?;
let edit = match snap.analysis.on_enter(position)? {
@ -321,7 +322,7 @@ pub(crate) fn handle_on_enter(
pub(crate) fn handle_on_type_formatting(
snap: GlobalStateSnapshot,
params: lsp_types::DocumentOnTypeFormattingParams,
) -> Result<Option<Vec<lsp_ext::SnippetTextEdit>>> {
) -> anyhow::Result<Option<Vec<lsp_ext::SnippetTextEdit>>> {
let _p = profile::span("handle_on_type_formatting");
let mut position = from_proto::file_position(&snap, params.text_document_position)?;
let line_index = snap.file_line_index(position.file_id)?;
@ -361,7 +362,7 @@ pub(crate) fn handle_on_type_formatting(
pub(crate) fn handle_document_symbol(
snap: GlobalStateSnapshot,
params: lsp_types::DocumentSymbolParams,
) -> Result<Option<lsp_types::DocumentSymbolResponse>> {
) -> anyhow::Result<Option<lsp_types::DocumentSymbolResponse>> {
let _p = profile::span("handle_document_symbol");
let file_id = from_proto::file_id(&snap, &params.text_document.uri)?;
let line_index = snap.file_line_index(file_id)?;
@ -450,7 +451,7 @@ pub(crate) fn handle_document_symbol(
pub(crate) fn handle_workspace_symbol(
snap: GlobalStateSnapshot,
params: WorkspaceSymbolParams,
) -> Result<Option<Vec<SymbolInformation>>> {
) -> anyhow::Result<Option<Vec<SymbolInformation>>> {
let _p = profile::span("handle_workspace_symbol");
let config = snap.config.workspace_symbol();
@ -513,7 +514,10 @@ pub(crate) fn handle_workspace_symbol(
(all_symbols, libs)
}
fn exec_query(snap: &GlobalStateSnapshot, query: Query) -> Result<Vec<SymbolInformation>> {
fn exec_query(
snap: &GlobalStateSnapshot,
query: Query,
) -> anyhow::Result<Vec<SymbolInformation>> {
let mut res = Vec::new();
for nav in snap.analysis.symbol_search(query)? {
let container_name = nav.container_name.as_ref().map(|v| v.to_string());
@ -542,7 +546,7 @@ pub(crate) fn handle_workspace_symbol(
pub(crate) fn handle_will_rename_files(
snap: GlobalStateSnapshot,
params: lsp_types::RenameFilesParams,
) -> Result<Option<lsp_types::WorkspaceEdit>> {
) -> anyhow::Result<Option<lsp_types::WorkspaceEdit>> {
let _p = profile::span("handle_will_rename_files");
let source_changes: Vec<SourceChange> = params
@ -604,7 +608,7 @@ pub(crate) fn handle_will_rename_files(
pub(crate) fn handle_goto_definition(
snap: GlobalStateSnapshot,
params: lsp_types::GotoDefinitionParams,
) -> Result<Option<lsp_types::GotoDefinitionResponse>> {
) -> anyhow::Result<Option<lsp_types::GotoDefinitionResponse>> {
let _p = profile::span("handle_goto_definition");
let position = from_proto::file_position(&snap, params.text_document_position_params)?;
let nav_info = match snap.analysis.goto_definition(position)? {
@ -619,7 +623,7 @@ pub(crate) fn handle_goto_definition(
pub(crate) fn handle_goto_declaration(
snap: GlobalStateSnapshot,
params: lsp_types::request::GotoDeclarationParams,
) -> Result<Option<lsp_types::request::GotoDeclarationResponse>> {
) -> anyhow::Result<Option<lsp_types::request::GotoDeclarationResponse>> {
let _p = profile::span("handle_goto_declaration");
let position = from_proto::file_position(&snap, params.text_document_position_params.clone())?;
let nav_info = match snap.analysis.goto_declaration(position)? {
@ -634,7 +638,7 @@ pub(crate) fn handle_goto_declaration(
pub(crate) fn handle_goto_implementation(
snap: GlobalStateSnapshot,
params: lsp_types::request::GotoImplementationParams,
) -> Result<Option<lsp_types::request::GotoImplementationResponse>> {
) -> anyhow::Result<Option<lsp_types::request::GotoImplementationResponse>> {
let _p = profile::span("handle_goto_implementation");
let position = from_proto::file_position(&snap, params.text_document_position_params)?;
let nav_info = match snap.analysis.goto_implementation(position)? {
@ -649,7 +653,7 @@ pub(crate) fn handle_goto_implementation(
pub(crate) fn handle_goto_type_definition(
snap: GlobalStateSnapshot,
params: lsp_types::request::GotoTypeDefinitionParams,
) -> Result<Option<lsp_types::request::GotoTypeDefinitionResponse>> {
) -> anyhow::Result<Option<lsp_types::request::GotoTypeDefinitionResponse>> {
let _p = profile::span("handle_goto_type_definition");
let position = from_proto::file_position(&snap, params.text_document_position_params)?;
let nav_info = match snap.analysis.goto_type_definition(position)? {
@ -664,7 +668,7 @@ pub(crate) fn handle_goto_type_definition(
pub(crate) fn handle_parent_module(
snap: GlobalStateSnapshot,
params: lsp_types::TextDocumentPositionParams,
) -> Result<Option<lsp_types::GotoDefinitionResponse>> {
) -> anyhow::Result<Option<lsp_types::GotoDefinitionResponse>> {
let _p = profile::span("handle_parent_module");
if let Ok(file_path) = &params.text_document.uri.to_file_path() {
if file_path.file_name().unwrap_or_default() == "Cargo.toml" {
@ -731,7 +735,7 @@ pub(crate) fn handle_parent_module(
pub(crate) fn handle_runnables(
snap: GlobalStateSnapshot,
params: lsp_ext::RunnablesParams,
) -> Result<Vec<lsp_ext::Runnable>> {
) -> anyhow::Result<Vec<lsp_ext::Runnable>> {
let _p = profile::span("handle_runnables");
let file_id = from_proto::file_id(&snap, &params.text_document.uri)?;
let line_index = snap.file_line_index(file_id)?;
@ -826,7 +830,7 @@ fn should_skip_for_offset(runnable: &Runnable, offset: Option<TextSize>) -> bool
pub(crate) fn handle_related_tests(
snap: GlobalStateSnapshot,
params: lsp_types::TextDocumentPositionParams,
) -> Result<Vec<lsp_ext::TestInfo>> {
) -> anyhow::Result<Vec<lsp_ext::TestInfo>> {
let _p = profile::span("handle_related_tests");
let position = from_proto::file_position(&snap, params)?;
@ -844,7 +848,7 @@ pub(crate) fn handle_related_tests(
pub(crate) fn handle_completion(
snap: GlobalStateSnapshot,
params: lsp_types::CompletionParams,
) -> Result<Option<lsp_types::CompletionResponse>> {
) -> anyhow::Result<Option<lsp_types::CompletionResponse>> {
let _p = profile::span("handle_completion");
let text_document_position = params.text_document_position.clone();
let position = from_proto::file_position(&snap, params.text_document_position)?;
@ -872,7 +876,7 @@ pub(crate) fn handle_completion(
pub(crate) fn handle_completion_resolve(
snap: GlobalStateSnapshot,
mut original_completion: CompletionItem,
) -> Result<CompletionItem> {
) -> anyhow::Result<CompletionItem> {
let _p = profile::span("handle_completion_resolve");
if !all_edits_are_disjoint(&original_completion, &[]) {
@ -928,7 +932,7 @@ pub(crate) fn handle_completion_resolve(
pub(crate) fn handle_folding_range(
snap: GlobalStateSnapshot,
params: FoldingRangeParams,
) -> Result<Option<Vec<FoldingRange>>> {
) -> anyhow::Result<Option<Vec<FoldingRange>>> {
let _p = profile::span("handle_folding_range");
let file_id = from_proto::file_id(&snap, &params.text_document.uri)?;
let folds = snap.analysis.folding_ranges(file_id)?;
@ -945,7 +949,7 @@ pub(crate) fn handle_folding_range(
pub(crate) fn handle_signature_help(
snap: GlobalStateSnapshot,
params: lsp_types::SignatureHelpParams,
) -> Result<Option<lsp_types::SignatureHelp>> {
) -> anyhow::Result<Option<lsp_types::SignatureHelp>> {
let _p = profile::span("handle_signature_help");
let position = from_proto::file_position(&snap, params.text_document_position_params)?;
let help = match snap.analysis.signature_help(position)? {
@ -960,7 +964,7 @@ pub(crate) fn handle_signature_help(
pub(crate) fn handle_hover(
snap: GlobalStateSnapshot,
params: lsp_ext::HoverParams,
) -> Result<Option<lsp_ext::Hover>> {
) -> anyhow::Result<Option<lsp_ext::Hover>> {
let _p = profile::span("handle_hover");
let range = match params.position {
PositionOrRange::Position(position) => Range::new(position, position),
@ -997,7 +1001,7 @@ pub(crate) fn handle_hover(
pub(crate) fn handle_prepare_rename(
snap: GlobalStateSnapshot,
params: lsp_types::TextDocumentPositionParams,
) -> Result<Option<PrepareRenameResponse>> {
) -> anyhow::Result<Option<PrepareRenameResponse>> {
let _p = profile::span("handle_prepare_rename");
let position = from_proto::file_position(&snap, params)?;
@ -1011,7 +1015,7 @@ pub(crate) fn handle_prepare_rename(
pub(crate) fn handle_rename(
snap: GlobalStateSnapshot,
params: RenameParams,
) -> Result<Option<WorkspaceEdit>> {
) -> anyhow::Result<Option<WorkspaceEdit>> {
let _p = profile::span("handle_rename");
let position = from_proto::file_position(&snap, params.text_document_position)?;
@ -1027,14 +1031,30 @@ pub(crate) fn handle_rename(
if !change.file_system_edits.is_empty() && snap.config.will_rename() {
change.source_file_edits.clear();
}
let workspace_edit = to_proto::workspace_edit(&snap, change)?;
if let Some(lsp_types::DocumentChanges::Operations(ops)) =
workspace_edit.document_changes.as_ref()
{
for op in ops {
if let lsp_types::DocumentChangeOperation::Op(doc_change_op) = op {
if let Err(err) =
resource_ops_supported(&snap.config, resolve_resource_op(doc_change_op))
{
return Err(err);
}
}
}
}
Ok(Some(workspace_edit))
}
pub(crate) fn handle_references(
snap: GlobalStateSnapshot,
params: lsp_types::ReferenceParams,
) -> Result<Option<Vec<Location>>> {
) -> anyhow::Result<Option<Vec<Location>>> {
let _p = profile::span("handle_references");
let position = from_proto::file_position(&snap, params.text_document_position)?;
@ -1077,7 +1097,7 @@ pub(crate) fn handle_references(
pub(crate) fn handle_formatting(
snap: GlobalStateSnapshot,
params: lsp_types::DocumentFormattingParams,
) -> Result<Option<Vec<lsp_types::TextEdit>>> {
) -> anyhow::Result<Option<Vec<lsp_types::TextEdit>>> {
let _p = profile::span("handle_formatting");
run_rustfmt(&snap, params.text_document, None)
@ -1086,7 +1106,7 @@ pub(crate) fn handle_formatting(
pub(crate) fn handle_range_formatting(
snap: GlobalStateSnapshot,
params: lsp_types::DocumentRangeFormattingParams,
) -> Result<Option<Vec<lsp_types::TextEdit>>> {
) -> anyhow::Result<Option<Vec<lsp_types::TextEdit>>> {
let _p = profile::span("handle_range_formatting");
run_rustfmt(&snap, params.text_document, Some(params.range))
@ -1095,7 +1115,7 @@ pub(crate) fn handle_range_formatting(
pub(crate) fn handle_code_action(
snap: GlobalStateSnapshot,
params: lsp_types::CodeActionParams,
) -> Result<Option<Vec<lsp_ext::CodeAction>>> {
) -> anyhow::Result<Option<Vec<lsp_ext::CodeAction>>> {
let _p = profile::span("handle_code_action");
if !snap.config.code_action_literals() {
@ -1134,6 +1154,21 @@ pub(crate) fn handle_code_action(
let resolve_data =
if code_action_resolve_cap { Some((index, params.clone())) } else { None };
let code_action = to_proto::code_action(&snap, assist, resolve_data)?;
// Check if the client supports the necessary `ResourceOperation`s.
let changes = code_action.edit.as_ref().and_then(|it| it.document_changes.as_ref());
if let Some(changes) = changes {
for change in changes {
if let lsp_ext::SnippetDocumentChangeOperation::Op(res_op) = change {
if let Err(err) =
resource_ops_supported(&snap.config, resolve_resource_op(res_op))
{
return Err(err);
}
}
}
}
res.push(code_action)
}
@ -1158,7 +1193,7 @@ pub(crate) fn handle_code_action(
pub(crate) fn handle_code_action_resolve(
snap: GlobalStateSnapshot,
mut code_action: lsp_ext::CodeAction,
) -> Result<lsp_ext::CodeAction> {
) -> anyhow::Result<lsp_ext::CodeAction> {
let _p = profile::span("handle_code_action_resolve");
let params = match code_action.data.take() {
Some(it) => it,
@ -1216,10 +1251,25 @@ pub(crate) fn handle_code_action_resolve(
let ca = to_proto::code_action(&snap, assist.clone(), None)?;
code_action.edit = ca.edit;
code_action.command = ca.command;
if let Some(edit) = code_action.edit.as_ref() {
if let Some(changes) = edit.document_changes.as_ref() {
for change in changes {
if let lsp_ext::SnippetDocumentChangeOperation::Op(res_op) = change {
if let Err(err) =
resource_ops_supported(&snap.config, resolve_resource_op(res_op))
{
return Err(err);
}
}
}
}
}
Ok(code_action)
}
fn parse_action_id(action_id: &str) -> Result<(usize, SingleResolve), String> {
fn parse_action_id(action_id: &str) -> anyhow::Result<(usize, SingleResolve), String> {
let id_parts = action_id.split(':').collect::<Vec<_>>();
match id_parts.as_slice() {
[assist_id_string, assist_kind_string, index_string] => {
@ -1237,7 +1287,7 @@ fn parse_action_id(action_id: &str) -> Result<(usize, SingleResolve), String> {
pub(crate) fn handle_code_lens(
snap: GlobalStateSnapshot,
params: lsp_types::CodeLensParams,
) -> Result<Option<Vec<CodeLens>>> {
) -> anyhow::Result<Option<Vec<CodeLens>>> {
let _p = profile::span("handle_code_lens");
let lens_config = snap.config.lens();
@ -1280,8 +1330,10 @@ pub(crate) fn handle_code_lens(
pub(crate) fn handle_code_lens_resolve(
snap: GlobalStateSnapshot,
code_lens: CodeLens,
) -> Result<CodeLens> {
let Some(annotation) = from_proto::annotation(&snap, code_lens.clone())? else { return Ok(code_lens) };
) -> anyhow::Result<CodeLens> {
let Some(annotation) = from_proto::annotation(&snap, code_lens.clone())? else {
return Ok(code_lens);
};
let annotation = snap.analysis.resolve_annotation(annotation)?;
let mut acc = Vec::new();
@ -1301,7 +1353,7 @@ pub(crate) fn handle_code_lens_resolve(
pub(crate) fn handle_document_highlight(
snap: GlobalStateSnapshot,
params: lsp_types::DocumentHighlightParams,
) -> Result<Option<Vec<lsp_types::DocumentHighlight>>> {
) -> anyhow::Result<Option<Vec<lsp_types::DocumentHighlight>>> {
let _p = profile::span("handle_document_highlight");
let position = from_proto::file_position(&snap, params.text_document_position_params)?;
let line_index = snap.file_line_index(position.file_id)?;
@ -1323,7 +1375,7 @@ pub(crate) fn handle_document_highlight(
pub(crate) fn handle_ssr(
snap: GlobalStateSnapshot,
params: lsp_ext::SsrParams,
) -> Result<lsp_types::WorkspaceEdit> {
) -> anyhow::Result<lsp_types::WorkspaceEdit> {
let _p = profile::span("handle_ssr");
let selections = params
.selections
@ -1343,7 +1395,7 @@ pub(crate) fn handle_ssr(
pub(crate) fn handle_inlay_hints(
snap: GlobalStateSnapshot,
params: InlayHintParams,
) -> Result<Option<Vec<InlayHint>>> {
) -> anyhow::Result<Option<Vec<InlayHint>>> {
let _p = profile::span("handle_inlay_hints");
let document_uri = &params.text_document.uri;
let FileRange { file_id, range } = from_proto::file_range(
@ -1365,7 +1417,7 @@ pub(crate) fn handle_inlay_hints(
pub(crate) fn handle_inlay_hints_resolve(
_snap: GlobalStateSnapshot,
hint: InlayHint,
) -> Result<InlayHint> {
) -> anyhow::Result<InlayHint> {
let _p = profile::span("handle_inlay_hints_resolve");
Ok(hint)
}
@ -1373,7 +1425,7 @@ pub(crate) fn handle_inlay_hints_resolve(
pub(crate) fn handle_call_hierarchy_prepare(
snap: GlobalStateSnapshot,
params: CallHierarchyPrepareParams,
) -> Result<Option<Vec<CallHierarchyItem>>> {
) -> anyhow::Result<Option<Vec<CallHierarchyItem>>> {
let _p = profile::span("handle_call_hierarchy_prepare");
let position = from_proto::file_position(&snap, params.text_document_position_params)?;
@ -1395,7 +1447,7 @@ pub(crate) fn handle_call_hierarchy_prepare(
pub(crate) fn handle_call_hierarchy_incoming(
snap: GlobalStateSnapshot,
params: CallHierarchyIncomingCallsParams,
) -> Result<Option<Vec<CallHierarchyIncomingCall>>> {
) -> anyhow::Result<Option<Vec<CallHierarchyIncomingCall>>> {
let _p = profile::span("handle_call_hierarchy_incoming");
let item = params.item;
@ -1430,7 +1482,7 @@ pub(crate) fn handle_call_hierarchy_incoming(
pub(crate) fn handle_call_hierarchy_outgoing(
snap: GlobalStateSnapshot,
params: CallHierarchyOutgoingCallsParams,
) -> Result<Option<Vec<CallHierarchyOutgoingCall>>> {
) -> anyhow::Result<Option<Vec<CallHierarchyOutgoingCall>>> {
let _p = profile::span("handle_call_hierarchy_outgoing");
let item = params.item;
@ -1465,7 +1517,7 @@ pub(crate) fn handle_call_hierarchy_outgoing(
pub(crate) fn handle_semantic_tokens_full(
snap: GlobalStateSnapshot,
params: SemanticTokensParams,
) -> Result<Option<SemanticTokensResult>> {
) -> anyhow::Result<Option<SemanticTokensResult>> {
let _p = profile::span("handle_semantic_tokens_full");
let file_id = from_proto::file_id(&snap, &params.text_document.uri)?;
@ -1495,7 +1547,7 @@ pub(crate) fn handle_semantic_tokens_full(
pub(crate) fn handle_semantic_tokens_full_delta(
snap: GlobalStateSnapshot,
params: SemanticTokensDeltaParams,
) -> Result<Option<SemanticTokensFullDeltaResult>> {
) -> anyhow::Result<Option<SemanticTokensFullDeltaResult>> {
let _p = profile::span("handle_semantic_tokens_full_delta");
let file_id = from_proto::file_id(&snap, &params.text_document.uri)?;
@ -1535,7 +1587,7 @@ pub(crate) fn handle_semantic_tokens_full_delta(
pub(crate) fn handle_semantic_tokens_range(
snap: GlobalStateSnapshot,
params: SemanticTokensRangeParams,
) -> Result<Option<SemanticTokensRangeResult>> {
) -> anyhow::Result<Option<SemanticTokensRangeResult>> {
let _p = profile::span("handle_semantic_tokens_range");
let frange = from_proto::file_range(&snap, params.text_document, params.range)?;
@ -1561,7 +1613,7 @@ pub(crate) fn handle_semantic_tokens_range(
pub(crate) fn handle_open_docs(
snap: GlobalStateSnapshot,
params: lsp_types::TextDocumentPositionParams,
) -> Result<ExternalDocsResponse> {
) -> anyhow::Result<ExternalDocsResponse> {
let _p = profile::span("handle_open_docs");
let position = from_proto::file_position(&snap, params)?;
@ -1582,9 +1634,9 @@ pub(crate) fn handle_open_docs(
let Ok(remote_urls) = snap.analysis.external_docs(position, target_dir, sysroot) else {
return if snap.config.local_docs() {
Ok(ExternalDocsResponse::WithLocal(Default::default()))
} else {
} else {
Ok(ExternalDocsResponse::Simple(None))
}
};
};
let web = remote_urls.web_url.and_then(|it| Url::parse(&it).ok());
@ -1600,7 +1652,7 @@ pub(crate) fn handle_open_docs(
pub(crate) fn handle_open_cargo_toml(
snap: GlobalStateSnapshot,
params: lsp_ext::OpenCargoTomlParams,
) -> Result<Option<lsp_types::GotoDefinitionResponse>> {
) -> anyhow::Result<Option<lsp_types::GotoDefinitionResponse>> {
let _p = profile::span("handle_open_cargo_toml");
let file_id = from_proto::file_id(&snap, &params.text_document.uri)?;
@ -1618,7 +1670,7 @@ pub(crate) fn handle_open_cargo_toml(
pub(crate) fn handle_move_item(
snap: GlobalStateSnapshot,
params: lsp_ext::MoveItemParams,
) -> Result<Vec<lsp_ext::SnippetTextEdit>> {
) -> anyhow::Result<Vec<lsp_ext::SnippetTextEdit>> {
let _p = profile::span("handle_move_item");
let file_id = from_proto::file_id(&snap, &params.text_document.uri)?;
let range = from_proto::file_range(&snap, params.text_document, params.range)?;
@ -1637,6 +1689,34 @@ pub(crate) fn handle_move_item(
}
}
pub(crate) fn handle_view_recursive_memory_layout(
snap: GlobalStateSnapshot,
params: lsp_types::TextDocumentPositionParams,
) -> anyhow::Result<Option<lsp_ext::RecursiveMemoryLayout>> {
let _p = profile::span("view_recursive_memory_layout");
let file_id = from_proto::file_id(&snap, &params.text_document.uri)?;
let line_index = snap.file_line_index(file_id)?;
let offset = from_proto::offset(&line_index, params.position)?;
let res = snap.analysis.get_recursive_memory_layout(FilePosition { file_id, offset })?;
Ok(res.map(|it| lsp_ext::RecursiveMemoryLayout {
nodes: it
.nodes
.iter()
.map(|n| lsp_ext::MemoryLayoutNode {
item_name: n.item_name.clone(),
typename: n.typename.clone(),
size: n.size,
offset: n.offset,
alignment: n.alignment,
parent_idx: n.parent_idx,
children_start: n.children_start,
children_len: n.children_len,
})
.collect(),
}))
}
fn to_command_link(command: lsp_types::Command, tooltip: String) -> lsp_ext::CommandLink {
lsp_ext::CommandLink { tooltip: Some(tooltip), command }
}
@ -1792,7 +1872,7 @@ fn run_rustfmt(
snap: &GlobalStateSnapshot,
text_document: TextDocumentIdentifier,
range: Option<lsp_types::Range>,
) -> Result<Option<Vec<lsp_types::TextEdit>>> {
) -> anyhow::Result<Option<Vec<lsp_types::TextEdit>>> {
let file_id = from_proto::file_id(snap, &text_document.uri)?;
let file = snap.analysis.file_text(file_id)?;
@ -1942,7 +2022,7 @@ fn run_rustfmt(
pub(crate) fn fetch_dependency_list(
state: GlobalStateSnapshot,
_params: FetchDependencyListParams,
) -> Result<FetchDependencyListResult> {
) -> anyhow::Result<FetchDependencyListResult> {
let crates = state.analysis.fetch_crates()?;
let crate_infos = crates
.into_iter()
@ -1987,3 +2067,42 @@ fn to_url(path: VfsPath) -> Option<Url> {
let str_path = path.as_os_str().to_str()?;
Url::from_file_path(str_path).ok()
}
fn resource_ops_supported(config: &Config, kind: ResourceOperationKind) -> anyhow::Result<()> {
#[rustfmt::skip]
let resops = (|| {
config
.caps()
.workspace
.as_ref()?
.workspace_edit
.as_ref()?
.resource_operations
.as_ref()
})();
if !matches!(resops, Some(resops) if resops.contains(&kind)) {
return Err(LspError::new(
ErrorCode::RequestFailed as i32,
format!(
"Client does not support {} capability.",
match kind {
ResourceOperationKind::Create => "create",
ResourceOperationKind::Rename => "rename",
ResourceOperationKind::Delete => "delete",
}
),
)
.into());
}
Ok(())
}
fn resolve_resource_op(op: &ResourceOp) -> ResourceOperationKind {
match op {
ResourceOp::Create(_) => ResourceOperationKind::Create,
ResourceOp::Rename(_) => ResourceOperationKind::Rename,
ResourceOp::Delete(_) => ResourceOperationKind::Delete,
}
}

View file

@ -20,7 +20,7 @@ use test_utils::project_root;
use triomphe::Arc;
use vfs::{AbsPathBuf, VfsPath};
use crate::cli::load_cargo::{load_workspace_at, LoadCargoConfig, ProcMacroServerChoice};
use load_cargo::{load_workspace_at, LoadCargoConfig, ProcMacroServerChoice};
#[test]
fn integrated_highlighting_benchmark() {

View file

@ -54,13 +54,12 @@ use serde::de::DeserializeOwned;
pub use crate::{caps::server_capabilities, main_loop::main_loop, version::version};
pub type Error = Box<dyn std::error::Error + Send + Sync>;
pub type Result<T, E = Error> = std::result::Result<T, E>;
pub fn from_json<T: DeserializeOwned>(what: &'static str, json: &serde_json::Value) -> Result<T> {
let res = serde_json::from_value(json.clone())
.map_err(|e| format!("Failed to deserialize {what}: {e}; {json}"))?;
Ok(res)
pub fn from_json<T: DeserializeOwned>(
what: &'static str,
json: &serde_json::Value,
) -> anyhow::Result<T> {
serde_json::from_value(json.clone())
.map_err(|e| anyhow::format_err!("Failed to deserialize {what}: {e}; {json}"))
}
#[derive(Debug)]

View file

@ -182,6 +182,33 @@ pub struct ExpandedMacro {
pub expansion: String,
}
pub enum ViewRecursiveMemoryLayout {}
impl Request for ViewRecursiveMemoryLayout {
type Params = lsp_types::TextDocumentPositionParams;
type Result = Option<RecursiveMemoryLayout>;
const METHOD: &'static str = "rust-analyzer/viewRecursiveMemoryLayout";
}
#[derive(Deserialize, Serialize, Debug)]
#[serde(rename_all = "camelCase")]
pub struct RecursiveMemoryLayout {
pub nodes: Vec<MemoryLayoutNode>,
}
#[derive(Deserialize, Serialize, Debug)]
#[serde(rename_all = "camelCase")]
pub struct MemoryLayoutNode {
pub item_name: String,
pub typename: String,
pub size: u64,
pub offset: u64,
pub alignment: u64,
pub parent_idx: i64,
pub children_start: i64,
pub children_len: u64,
}
pub enum CancelFlycheck {}
impl Notification for CancelFlycheck {

View file

@ -23,10 +23,9 @@ use crate::{
lsp_ext,
lsp_utils::{notification_is, Progress},
reload::{BuildDataProgress, ProcMacroProgress, ProjectWorkspaceProgress},
Result,
};
pub fn main_loop(config: Config, connection: Connection) -> Result<()> {
pub fn main_loop(config: Config, connection: Connection) -> anyhow::Result<()> {
tracing::info!("initial config: {:#?}", config);
// Windows scheduler implements priority boosts: if thread waits for an
@ -109,7 +108,7 @@ impl fmt::Debug for Event {
}
impl GlobalState {
fn run(mut self, inbox: Receiver<lsp_server::Message>) -> Result<()> {
fn run(mut self, inbox: Receiver<lsp_server::Message>) -> anyhow::Result<()> {
self.update_status_or_notify();
if self.config.did_save_text_document_dynamic_registration() {
@ -134,7 +133,7 @@ impl GlobalState {
self.handle_event(event)?;
}
Err("client exited without proper shutdown sequence".into())
anyhow::bail!("client exited without proper shutdown sequence")
}
fn register_did_save_capability(&mut self) {
@ -191,7 +190,7 @@ impl GlobalState {
}
}
fn handle_event(&mut self, event: Event) -> Result<()> {
fn handle_event(&mut self, event: Event) -> anyhow::Result<()> {
let loop_start = Instant::now();
// NOTE: don't count blocking select! call as a loop-turn time
let _p = profile::span("GlobalState::handle_event");
@ -754,11 +753,12 @@ impl GlobalState {
)
.on::<lsp_types::request::WillRenameFiles>(handlers::handle_will_rename_files)
.on::<lsp_ext::Ssr>(handlers::handle_ssr)
.on::<lsp_ext::ViewRecursiveMemoryLayout>(handlers::handle_view_recursive_memory_layout)
.finish();
}
/// Handles an incoming notification.
fn on_notification(&mut self, not: Notification) -> Result<()> {
fn on_notification(&mut self, not: Notification) -> anyhow::Result<()> {
use crate::handlers::notification as handlers;
use lsp_types::notification as notifs;
@ -843,11 +843,7 @@ impl GlobalState {
d.code.as_str().to_string(),
)),
code_description: Some(lsp_types::CodeDescription {
href: lsp_types::Url::parse(&format!(
"https://rust-analyzer.github.io/manual.html#{}",
d.code.as_str()
))
.unwrap(),
href: lsp_types::Url::parse(&d.code.url()).unwrap(),
}),
source: Some("rust-analyzer".to_string()),
message: d.message,

View file

@ -12,26 +12,22 @@
//! correct. Instead, we try to provide a best-effort service. Even if the
//! project is currently loading and we don't have a full project model, we
//! still want to respond to various requests.
use std::{collections::hash_map::Entry, iter, mem, sync};
use std::{iter, mem};
use flycheck::{FlycheckConfig, FlycheckHandle};
use hir::db::DefDatabase;
use ide::Change;
use ide_db::{
base_db::{
salsa::Durability, CrateGraph, Env, ProcMacro, ProcMacroExpander, ProcMacroExpansionError,
ProcMacroKind, ProcMacroLoadResult, ProcMacroPaths, ProcMacros, SourceRoot, VfsPath,
},
base_db::{salsa::Durability, CrateGraph, ProcMacroPaths, ProcMacros},
FxHashMap,
};
use itertools::Itertools;
use proc_macro_api::{MacroDylib, ProcMacroServer};
use project_model::{PackageRoot, ProjectWorkspace, WorkspaceBuildScripts};
use load_cargo::{load_proc_macro, ProjectFolders};
use proc_macro_api::ProcMacroServer;
use project_model::{ProjectWorkspace, WorkspaceBuildScripts};
use rustc_hash::FxHashSet;
use stdx::{format_to, thread::ThreadIntent};
use syntax::SmolStr;
use triomphe::Arc;
use vfs::{file_set::FileSetConfig, AbsPath, AbsPathBuf, ChangeKind};
use vfs::{AbsPath, ChangeKind};
use crate::{
config::{Config, FilesWatcher, LinkedProject},
@ -41,8 +37,6 @@ use crate::{
op_queue::Cause,
};
use ::tt::token_id as tt;
#[derive(Debug)]
pub(crate) enum ProjectWorkspaceProgress {
Begin,
@ -307,7 +301,7 @@ impl GlobalState {
res.map_or_else(
|_| Err("proc macro crate is missing dylib".to_owned()),
|(crate_name, path)| {
progress(path.display().to_string());
progress(path.to_string());
client.as_ref().map_err(Clone::clone).and_then(|client| {
load_proc_macro(
client,
@ -340,7 +334,11 @@ impl GlobalState {
let _p = profile::span("GlobalState::switch_workspaces");
tracing::info!(%cause, "will switch workspaces");
let Some((workspaces, force_reload_crate_graph)) = self.fetch_workspaces_queue.last_op_result() else { return; };
let Some((workspaces, force_reload_crate_graph)) =
self.fetch_workspaces_queue.last_op_result()
else {
return;
};
if let Err(_) = self.fetch_workspace_error() {
if !self.workspaces.is_empty() {
@ -407,9 +405,9 @@ impl GlobalState {
.flat_map(|root| {
root.include.into_iter().flat_map(|it| {
[
format!("{}/**/*.rs", it.display()),
format!("{}/**/Cargo.toml", it.display()),
format!("{}/**/Cargo.lock", it.display()),
format!("{it}/**/*.rs"),
format!("{it}/**/Cargo.toml"),
format!("{it}/**/Cargo.lock"),
]
})
})
@ -447,17 +445,13 @@ impl GlobalState {
None => ws.find_sysroot_proc_macro_srv()?,
};
tracing::info!("Using proc-macro server at {}", path.display(),);
tracing::info!("Using proc-macro server at {path}");
ProcMacroServer::spawn(path.clone()).map_err(|err| {
tracing::error!(
"Failed to run proc-macro server from path {}, error: {:?}",
path.display(),
err
"Failed to run proc-macro server from path {path}, error: {err:?}",
);
anyhow::anyhow!(
"Failed to run proc-macro server from path {}, error: {:?}",
path.display(),
err
anyhow::format_err!(
"Failed to run proc-macro server from path {path}, error: {err:?}",
)
})
})
@ -534,7 +528,9 @@ impl GlobalState {
pub(super) fn fetch_workspace_error(&self) -> Result<(), String> {
let mut buf = String::new();
let Some((last_op_result, _)) = self.fetch_workspaces_queue.last_op_result() else { return Ok(()) };
let Some((last_op_result, _)) = self.fetch_workspaces_queue.last_op_result() else {
return Ok(());
};
if last_op_result.is_empty() {
stdx::format_to!(buf, "rust-analyzer failed to discover workspace");
} else {
@ -621,254 +617,6 @@ impl GlobalState {
}
}
#[derive(Default)]
pub(crate) struct ProjectFolders {
pub(crate) load: Vec<vfs::loader::Entry>,
pub(crate) watch: Vec<usize>,
pub(crate) source_root_config: SourceRootConfig,
}
impl ProjectFolders {
pub(crate) fn new(
workspaces: &[ProjectWorkspace],
global_excludes: &[AbsPathBuf],
) -> ProjectFolders {
let mut res = ProjectFolders::default();
let mut fsc = FileSetConfig::builder();
let mut local_filesets = vec![];
// Dedup source roots
// Depending on the project setup, we can have duplicated source roots, or for example in
// the case of the rustc workspace, we can end up with two source roots that are almost the
// same but not quite, like:
// PackageRoot { is_local: false, include: [AbsPathBuf(".../rust/src/tools/miri/cargo-miri")], exclude: [] }
// PackageRoot {
// is_local: true,
// include: [AbsPathBuf(".../rust/src/tools/miri/cargo-miri"), AbsPathBuf(".../rust/build/x86_64-pc-windows-msvc/stage0-tools/x86_64-pc-windows-msvc/release/build/cargo-miri-85801cd3d2d1dae4/out")],
// exclude: [AbsPathBuf(".../rust/src/tools/miri/cargo-miri/.git"), AbsPathBuf(".../rust/src/tools/miri/cargo-miri/target")]
// }
//
// The first one comes from the explicit rustc workspace which points to the rustc workspace itself
// The second comes from the rustc workspace that we load as the actual project workspace
// These `is_local` differing in this kind of way gives us problems, especially when trying to filter diagnostics as we don't report diagnostics for external libraries.
// So we need to deduplicate these, usually it would be enough to deduplicate by `include`, but as the rustc example shows here that doesn't work,
// so we need to also coalesce the includes if they overlap.
let mut roots: Vec<_> = workspaces
.iter()
.flat_map(|ws| ws.to_roots())
.update(|root| root.include.sort())
.sorted_by(|a, b| a.include.cmp(&b.include))
.collect();
// map that tracks indices of overlapping roots
let mut overlap_map = FxHashMap::<_, Vec<_>>::default();
let mut done = false;
while !mem::replace(&mut done, true) {
// maps include paths to indices of the corresponding root
let mut include_to_idx = FxHashMap::default();
// Find and note down the indices of overlapping roots
for (idx, root) in roots.iter().enumerate().filter(|(_, it)| !it.include.is_empty()) {
for include in &root.include {
match include_to_idx.entry(include) {
Entry::Occupied(e) => {
overlap_map.entry(*e.get()).or_default().push(idx);
}
Entry::Vacant(e) => {
e.insert(idx);
}
}
}
}
for (k, v) in overlap_map.drain() {
done = false;
for v in v {
let r = mem::replace(
&mut roots[v],
PackageRoot { is_local: false, include: vec![], exclude: vec![] },
);
roots[k].is_local |= r.is_local;
roots[k].include.extend(r.include);
roots[k].exclude.extend(r.exclude);
}
roots[k].include.sort();
roots[k].exclude.sort();
roots[k].include.dedup();
roots[k].exclude.dedup();
}
}
for root in roots.into_iter().filter(|it| !it.include.is_empty()) {
let file_set_roots: Vec<VfsPath> =
root.include.iter().cloned().map(VfsPath::from).collect();
let entry = {
let mut dirs = vfs::loader::Directories::default();
dirs.extensions.push("rs".into());
dirs.include.extend(root.include);
dirs.exclude.extend(root.exclude);
for excl in global_excludes {
if dirs
.include
.iter()
.any(|incl| incl.starts_with(excl) || excl.starts_with(incl))
{
dirs.exclude.push(excl.clone());
}
}
vfs::loader::Entry::Directories(dirs)
};
if root.is_local {
res.watch.push(res.load.len());
}
res.load.push(entry);
if root.is_local {
local_filesets.push(fsc.len());
}
fsc.add_file_set(file_set_roots)
}
let fsc = fsc.build();
res.source_root_config = SourceRootConfig { fsc, local_filesets };
res
}
}
#[derive(Default, Debug)]
pub(crate) struct SourceRootConfig {
pub(crate) fsc: FileSetConfig,
pub(crate) local_filesets: Vec<usize>,
}
impl SourceRootConfig {
pub(crate) fn partition(&self, vfs: &vfs::Vfs) -> Vec<SourceRoot> {
let _p = profile::span("SourceRootConfig::partition");
self.fsc
.partition(vfs)
.into_iter()
.enumerate()
.map(|(idx, file_set)| {
let is_local = self.local_filesets.contains(&idx);
if is_local {
SourceRoot::new_local(file_set)
} else {
SourceRoot::new_library(file_set)
}
})
.collect()
}
}
/// Load the proc-macros for the given lib path, replacing all expanders whose names are in `dummy_replace`
/// with an identity dummy expander.
pub(crate) fn load_proc_macro(
server: &ProcMacroServer,
path: &AbsPath,
dummy_replace: &[Box<str>],
) -> ProcMacroLoadResult {
let res: Result<Vec<_>, String> = (|| {
let dylib = MacroDylib::new(path.to_path_buf());
let vec = server.load_dylib(dylib).map_err(|e| format!("{e}"))?;
if vec.is_empty() {
return Err("proc macro library returned no proc macros".to_string());
}
Ok(vec
.into_iter()
.map(|expander| expander_to_proc_macro(expander, dummy_replace))
.collect())
})();
return match res {
Ok(proc_macros) => {
tracing::info!(
"Loaded proc-macros for {}: {:?}",
path.display(),
proc_macros.iter().map(|it| it.name.clone()).collect::<Vec<_>>()
);
Ok(proc_macros)
}
Err(e) => {
tracing::warn!("proc-macro loading for {} failed: {e}", path.display());
Err(e)
}
};
fn expander_to_proc_macro(
expander: proc_macro_api::ProcMacro,
dummy_replace: &[Box<str>],
) -> ProcMacro {
let name = SmolStr::from(expander.name());
let kind = match expander.kind() {
proc_macro_api::ProcMacroKind::CustomDerive => ProcMacroKind::CustomDerive,
proc_macro_api::ProcMacroKind::FuncLike => ProcMacroKind::FuncLike,
proc_macro_api::ProcMacroKind::Attr => ProcMacroKind::Attr,
};
let expander: sync::Arc<dyn ProcMacroExpander> =
if dummy_replace.iter().any(|replace| &**replace == name) {
match kind {
ProcMacroKind::Attr => sync::Arc::new(IdentityExpander),
_ => sync::Arc::new(EmptyExpander),
}
} else {
sync::Arc::new(Expander(expander))
};
ProcMacro { name, kind, expander }
}
#[derive(Debug)]
struct Expander(proc_macro_api::ProcMacro);
impl ProcMacroExpander for Expander {
fn expand(
&self,
subtree: &tt::Subtree,
attrs: Option<&tt::Subtree>,
env: &Env,
) -> Result<tt::Subtree, ProcMacroExpansionError> {
let env = env.iter().map(|(k, v)| (k.to_string(), v.to_string())).collect();
match self.0.expand(subtree, attrs, env) {
Ok(Ok(subtree)) => Ok(subtree),
Ok(Err(err)) => Err(ProcMacroExpansionError::Panic(err.0)),
Err(err) => Err(ProcMacroExpansionError::System(err.to_string())),
}
}
}
/// Dummy identity expander, used for attribute proc-macros that are deliberately ignored by the user.
#[derive(Debug)]
struct IdentityExpander;
impl ProcMacroExpander for IdentityExpander {
fn expand(
&self,
subtree: &tt::Subtree,
_: Option<&tt::Subtree>,
_: &Env,
) -> Result<tt::Subtree, ProcMacroExpansionError> {
Ok(subtree.clone())
}
}
/// Empty expander, used for proc-macros that are deliberately ignored by the user.
#[derive(Debug)]
struct EmptyExpander;
impl ProcMacroExpander for EmptyExpander {
fn expand(
&self,
_: &tt::Subtree,
_: Option<&tt::Subtree>,
_: &Env,
) -> Result<tt::Subtree, ProcMacroExpansionError> {
Ok(tt::Subtree::empty())
}
}
}
pub(crate) fn should_refresh_for_change(path: &AbsPath, change_kind: ChangeKind) -> bool {
const IMPLICIT_TARGET_FILES: &[&str] = &["build.rs", "src/main.rs", "src/lib.rs"];
const IMPLICIT_TARGET_DIRS: &[&str] = &["src/bin", "examples", "tests", "benches"];

View file

@ -94,7 +94,10 @@ pub(crate) fn document_highlight_kind(
pub(crate) fn diagnostic_severity(severity: Severity) -> lsp_types::DiagnosticSeverity {
match severity {
Severity::Error => lsp_types::DiagnosticSeverity::ERROR,
Severity::Warning => lsp_types::DiagnosticSeverity::WARNING,
Severity::WeakWarning => lsp_types::DiagnosticSeverity::HINT,
// unreachable
Severity::Allow => lsp_types::DiagnosticSeverity::INFORMATION,
}
}

View file

@ -13,6 +13,7 @@ use rust_analyzer::{config::Config, lsp_ext, main_loop};
use serde::Serialize;
use serde_json::{json, to_string_pretty, Value};
use test_utils::FixtureWithProjectMeta;
use tracing_subscriber::{prelude::*, Layer};
use vfs::AbsPathBuf;
use crate::testdir::TestDir;
@ -24,7 +25,7 @@ pub(crate) struct Project<'a> {
config: serde_json::Value,
}
impl<'a> Project<'a> {
impl Project<'_> {
pub(crate) fn with_fixture(fixture: &str) -> Project<'_> {
Project {
fixture,
@ -47,17 +48,17 @@ impl<'a> Project<'a> {
}
}
pub(crate) fn tmp_dir(mut self, tmp_dir: TestDir) -> Project<'a> {
pub(crate) fn tmp_dir(mut self, tmp_dir: TestDir) -> Self {
self.tmp_dir = Some(tmp_dir);
self
}
pub(crate) fn root(mut self, path: &str) -> Project<'a> {
pub(crate) fn root(mut self, path: &str) -> Self {
self.roots.push(path.into());
self
}
pub(crate) fn with_config(mut self, config: serde_json::Value) -> Project<'a> {
pub(crate) fn with_config(mut self, config: serde_json::Value) -> Self {
fn merge(dst: &mut serde_json::Value, src: serde_json::Value) {
match (dst, src) {
(Value::Object(dst), Value::Object(src)) => {
@ -76,10 +77,11 @@ impl<'a> Project<'a> {
let tmp_dir = self.tmp_dir.unwrap_or_else(TestDir::new);
static INIT: Once = Once::new();
INIT.call_once(|| {
tracing_subscriber::fmt()
.with_test_writer()
.with_env_filter(tracing_subscriber::EnvFilter::from_env("RA_LOG"))
.init();
let filter: tracing_subscriber::filter::Targets =
std::env::var("RA_LOG").ok().and_then(|it| it.parse().ok()).unwrap_or_default();
let layer =
tracing_subscriber::fmt::Layer::new().with_test_writer().with_filter(filter);
tracing_subscriber::Registry::default().with(layer).init();
profile::init_from(crate::PROFILE);
});
@ -111,6 +113,14 @@ impl<'a> Project<'a> {
relative_pattern_support: None,
},
),
workspace_edit: Some(lsp_types::WorkspaceEditClientCapabilities {
resource_operations: Some(vec![
lsp_types::ResourceOperationKind::Create,
lsp_types::ResourceOperationKind::Delete,
lsp_types::ResourceOperationKind::Rename,
]),
..Default::default()
}),
..Default::default()
}),
text_document: Some(lsp_types::TextDocumentClientCapabilities {