Skip to content

Commit 2995687

Browse files
committed
[ty] Implement streaming for workspace diagnostics
1 parent d8151f0 commit 2995687

File tree

15 files changed

+1102
-222
lines changed

15 files changed

+1102
-222
lines changed

crates/ty/src/lib.rs

Lines changed: 31 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -22,12 +22,13 @@ use colored::Colorize;
2222
use crossbeam::channel as crossbeam_channel;
2323
use rayon::ThreadPoolBuilder;
2424
use ruff_db::diagnostic::{Diagnostic, DisplayDiagnosticConfig, Severity};
25+
use ruff_db::files::File;
2526
use ruff_db::max_parallelism;
2627
use ruff_db::system::{OsSystem, SystemPath, SystemPathBuf};
2728
use salsa::plumbing::ZalsaDatabase;
2829
use ty_project::metadata::options::ProjectOptionsOverrides;
2930
use ty_project::watch::ProjectWatcher;
30-
use ty_project::{Db, watch};
31+
use ty_project::{CollectReporter, Db, watch};
3132
use ty_project::{ProjectDatabase, ProjectMetadata};
3233
use ty_server::run_server;
3334

@@ -275,7 +276,8 @@ impl MainLoop {
275276
rayon::spawn(move || {
276277
match salsa::Cancelled::catch(|| {
277278
let mut reporter = IndicatifReporter::from(self.printer);
278-
db.check_with_reporter(&mut reporter)
279+
db.check_with_reporter(&mut reporter);
280+
reporter.collector.into_sorted(&db)
279281
}) {
280282
Ok(result) => {
281283
// Send the result back to the main loop for printing.
@@ -394,8 +396,12 @@ impl MainLoop {
394396
}
395397
}
396398

399+
struct IndicatifReporter {
400+
collector: CollectReporter,
401+
state: IndicatifReporterState,
402+
}
397403
/// A progress reporter for `ty check`.
398-
enum IndicatifReporter {
404+
enum IndicatifReporterState {
399405
/// A constructed reporter that is not yet ready, contains the target for the progress bar.
400406
Pending(indicatif::ProgressDrawTarget),
401407
/// A reporter that is ready, containing a progress bar to report to.
@@ -408,18 +414,25 @@ enum IndicatifReporter {
408414

409415
impl From<Printer> for IndicatifReporter {
410416
fn from(printer: Printer) -> Self {
411-
Self::Pending(printer.progress_target())
417+
Self {
418+
collector: CollectReporter::default(),
419+
state: IndicatifReporterState::Pending(printer.progress_target()),
420+
}
412421
}
413422
}
414423

415424
impl ty_project::ProgressReporter for IndicatifReporter {
416425
fn set_files(&mut self, files: usize) {
426+
self.collector.set_files(files);
427+
417428
let target = match std::mem::replace(
418-
self,
419-
IndicatifReporter::Pending(indicatif::ProgressDrawTarget::hidden()),
429+
&mut self.state,
430+
IndicatifReporterState::Pending(indicatif::ProgressDrawTarget::hidden()),
420431
) {
421-
Self::Pending(target) => target,
422-
Self::Initialized(_) => panic!("The progress reporter should only be initialized once"),
432+
IndicatifReporterState::Pending(target) => target,
433+
IndicatifReporterState::Initialized(_) => {
434+
panic!("The progress reporter should only be initialized once")
435+
}
423436
};
424437

425438
let bar = indicatif::ProgressBar::with_draw_target(Some(files as u64), target);
@@ -431,19 +444,24 @@ impl ty_project::ProgressReporter for IndicatifReporter {
431444
.progress_chars("--"),
432445
);
433446
bar.set_message("Checking");
434-
*self = Self::Initialized(bar);
447+
self.state = IndicatifReporterState::Initialized(bar);
435448
}
436449

437-
fn report_file(&self, _file: &ruff_db::files::File) {
438-
match self {
439-
IndicatifReporter::Initialized(progress_bar) => {
450+
fn report_file(&self, db: &dyn Db, file: File, diagnostics: &[Diagnostic]) {
451+
self.collector.report_file(db, file, diagnostics);
452+
match &self.state {
453+
IndicatifReporterState::Initialized(progress_bar) => {
440454
progress_bar.inc(1);
441455
}
442-
IndicatifReporter::Pending(_) => {
456+
IndicatifReporterState::Pending(_) => {
443457
panic!("`report_file` called before `set_files`")
444458
}
445459
}
446460
}
461+
462+
fn report_diagnostics(&mut self, db: &dyn Db, diagnostics: Vec<Diagnostic>) {
463+
self.collector.report_diagnostics(db, diagnostics);
464+
}
447465
}
448466

449467
#[derive(Debug)]

crates/ty/tests/cli/rule_selection.rs

Lines changed: 10 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -863,10 +863,18 @@ fn overrides_unknown_rules() -> anyhow::Result<()> {
863863
),
864864
])?;
865865

866-
assert_cmd_snapshot!(case.command(), @r#"
866+
assert_cmd_snapshot!(case.command(), @r###"
867867
success: false
868868
exit_code: 1
869869
----- stdout -----
870+
error[division-by-zero]: Cannot divide object of type `Literal[4]` by zero
871+
--> main.py:2:5
872+
|
873+
2 | y = 4 / 0
874+
| ^^^^^
875+
|
876+
info: rule `division-by-zero` was selected in the configuration file
877+
870878
warning[unknown-rule]: Unknown lint rule `division-by-zer`
871879
--> pyproject.toml:10:1
872880
|
@@ -876,14 +884,6 @@ fn overrides_unknown_rules() -> anyhow::Result<()> {
876884
| ^^^^^^^^^^^^^^^
877885
|
878886
879-
error[division-by-zero]: Cannot divide object of type `Literal[4]` by zero
880-
--> main.py:2:5
881-
|
882-
2 | y = 4 / 0
883-
| ^^^^^
884-
|
885-
info: rule `division-by-zero` was selected in the configuration file
886-
887887
warning[division-by-zero]: Cannot divide object of type `Literal[4]` by zero
888888
--> tests/test_main.py:2:5
889889
|
@@ -896,7 +896,7 @@ fn overrides_unknown_rules() -> anyhow::Result<()> {
896896
897897
----- stderr -----
898898
WARN ty is pre-release software and not ready for production use. Expect to encounter bugs, missing features, and fatal errors.
899-
"#);
899+
"###);
900900

901901
Ok(())
902902
}

crates/ty_project/src/db.rs

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@ use std::{cmp, fmt};
55

66
pub use self::changes::ChangeResult;
77
use crate::metadata::settings::file_settings;
8-
use crate::{DEFAULT_LINT_REGISTRY, DummyReporter};
8+
use crate::{CollectReporter, DEFAULT_LINT_REGISTRY};
99
use crate::{ProgressReporter, Project, ProjectMetadata};
1010
use ruff_db::Db as SourceDb;
1111
use ruff_db::diagnostic::Diagnostic;
@@ -87,16 +87,18 @@ impl ProjectDatabase {
8787
///
8888
/// [`set_check_mode`]: ProjectDatabase::set_check_mode
8989
pub fn check(&self) -> Vec<Diagnostic> {
90-
self.project().check(self, &mut DummyReporter)
90+
let mut collector = CollectReporter::default();
91+
self.project().check(self, &mut collector);
92+
collector.into_sorted(self)
9193
}
9294

9395
/// Checks the files in the project and its dependencies, using the given reporter.
9496
///
9597
/// Use [`set_check_mode`] to update the check mode.
9698
///
9799
/// [`set_check_mode`]: ProjectDatabase::set_check_mode
98-
pub fn check_with_reporter(&self, reporter: &mut dyn ProgressReporter) -> Vec<Diagnostic> {
99-
self.project().check(self, reporter)
100+
pub fn check_with_reporter(&self, reporter: &mut dyn ProgressReporter) {
101+
self.project().check(self, reporter);
100102
}
101103

102104
#[tracing::instrument(level = "debug", skip(self))]

crates/ty_project/src/lib.rs

Lines changed: 46 additions & 35 deletions
Original file line numberDiff line numberDiff line change
@@ -128,16 +128,45 @@ pub trait ProgressReporter: Send + Sync {
128128
fn set_files(&mut self, files: usize);
129129

130130
/// Report the completion of a given file.
131-
fn report_file(&self, file: &File);
131+
fn report_file(&self, db: &dyn Db, file: File, diagnostics: &[Diagnostic]);
132+
133+
/// Reports settings or IO related diagnostics. The diagnostics
134+
/// can belong to different files or no file at all.
135+
/// But it's never a file for which [`Self::report_file`] gets called.
136+
fn report_diagnostics(&mut self, db: &dyn Db, diagnostics: Vec<Diagnostic>);
132137
}
133138

134-
/// A no-op implementation of [`ProgressReporter`].
139+
/// Reporter that collects all diagnostics into a `Vec`.
135140
#[derive(Default)]
136-
pub struct DummyReporter;
141+
pub struct CollectReporter(std::sync::Mutex<Vec<Diagnostic>>);
142+
143+
impl CollectReporter {
144+
pub fn into_sorted(self, db: &dyn Db) -> Vec<Diagnostic> {
145+
let mut diagnostics = self.0.into_inner().unwrap();
146+
diagnostics.sort_by(|left, right| {
147+
left.rendering_sort_key(db)
148+
.cmp(&right.rendering_sort_key(db))
149+
});
150+
diagnostics
151+
}
152+
}
137153

138-
impl ProgressReporter for DummyReporter {
154+
impl ProgressReporter for CollectReporter {
139155
fn set_files(&mut self, _files: usize) {}
140-
fn report_file(&self, _file: &File) {}
156+
fn report_file(&self, _db: &dyn Db, _file: File, diagnostics: &[Diagnostic]) {
157+
if diagnostics.is_empty() {
158+
return;
159+
}
160+
161+
self.0
162+
.lock()
163+
.unwrap()
164+
.extend(diagnostics.iter().map(Clone::clone));
165+
}
166+
167+
fn report_diagnostics(&mut self, _db: &dyn Db, diagnostics: Vec<Diagnostic>) {
168+
self.0.get_mut().unwrap().extend(diagnostics);
169+
}
141170
}
142171

143172
#[salsa::tracked]
@@ -225,11 +254,7 @@ impl Project {
225254
}
226255

227256
/// Checks the project and its dependencies according to the project's check mode.
228-
pub(crate) fn check(
229-
self,
230-
db: &ProjectDatabase,
231-
reporter: &mut dyn ProgressReporter,
232-
) -> Vec<Diagnostic> {
257+
pub(crate) fn check(self, db: &ProjectDatabase, reporter: &mut dyn ProgressReporter) {
233258
let project_span = tracing::debug_span!("Project::check");
234259
let _span = project_span.enter();
235260

@@ -239,12 +264,11 @@ impl Project {
239264
name = self.name(db)
240265
);
241266

242-
let mut diagnostics: Vec<Diagnostic> = Vec::new();
243-
diagnostics.extend(
244-
self.settings_diagnostics(db)
245-
.iter()
246-
.map(OptionDiagnostic::to_diagnostic),
247-
);
267+
let mut diagnostics: Vec<Diagnostic> = self
268+
.settings_diagnostics(db)
269+
.iter()
270+
.map(OptionDiagnostic::to_diagnostic)
271+
.collect();
248272

249273
let files = ProjectFiles::new(db, self);
250274
reporter.set_files(files.len());
@@ -256,30 +280,27 @@ impl Project {
256280
.map(IOErrorDiagnostic::to_diagnostic),
257281
);
258282

283+
reporter.report_diagnostics(db, diagnostics);
284+
259285
let open_files = self.open_files(db);
260286
let check_start = ruff_db::Instant::now();
261-
let file_diagnostics = std::sync::Mutex::new(vec![]);
262287

263288
{
264289
let db = db.clone();
265-
let file_diagnostics = &file_diagnostics;
266290
let project_span = &project_span;
267-
let reporter = &reporter;
268291

269292
rayon::scope(move |scope| {
270293
for file in &files {
271294
let db = db.clone();
295+
let reporter = &*reporter;
272296
scope.spawn(move |_| {
273297
let check_file_span =
274298
tracing::debug_span!(parent: project_span, "check_file", ?file);
275299
let _entered = check_file_span.entered();
276300

277301
match check_file_impl(&db, file) {
278302
Ok(diagnostics) => {
279-
file_diagnostics
280-
.lock()
281-
.unwrap()
282-
.extend(diagnostics.iter().map(Clone::clone));
303+
reporter.report_file(&db, file, diagnostics);
283304

284305
// This is outside `check_file_impl` to avoid that opening or closing
285306
// a file invalidates the `check_file_impl` query of every file!
@@ -295,28 +316,18 @@ impl Project {
295316
}
296317
}
297318
Err(io_error) => {
298-
file_diagnostics.lock().unwrap().push(io_error.clone());
319+
reporter.report_file(&db, file, std::slice::from_ref(io_error));
299320
}
300321
}
301-
302-
reporter.report_file(&file);
303322
});
304323
}
305324
});
306-
}
325+
};
307326

308327
tracing::debug!(
309328
"Checking all files took {:.3}s",
310329
check_start.elapsed().as_secs_f64(),
311330
);
312-
313-
let mut file_diagnostics = file_diagnostics.into_inner().unwrap();
314-
file_diagnostics.sort_by(|left, right| {
315-
left.rendering_sort_key(db)
316-
.cmp(&right.rendering_sort_key(db))
317-
});
318-
diagnostics.extend(file_diagnostics);
319-
diagnostics
320331
}
321332

322333
pub(crate) fn check_file(self, db: &dyn Db, file: File) -> Vec<Diagnostic> {

crates/ty_server/src/lib.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@ use lsp_server::Connection;
55
use ruff_db::system::{OsSystem, SystemPathBuf};
66

77
pub use crate::logging::{LogLevel, init_logging};
8-
pub use crate::server::Server;
8+
pub use crate::server::{PartialWorkspaceProgress, PartialWorkspaceProgressParams, Server};
99
pub use crate::session::{ClientOptions, DiagnosticMode};
1010
pub use document::{NotebookDocument, PositionEncoding, TextDocument};
1111
pub(crate) use session::{DocumentQuery, Session};

crates/ty_server/src/server.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,7 @@ pub(crate) use main_loop::{
2929
Action, ConnectionSender, Event, MainLoopReceiver, MainLoopSender, SendRequest,
3030
};
3131
pub(crate) type Result<T> = std::result::Result<T, api::Error>;
32+
pub use api::{PartialWorkspaceProgress, PartialWorkspaceProgressParams};
3233

3334
pub struct Server {
3435
connection: Connection,

crates/ty_server/src/server/api.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@ use self::traits::{NotificationHandler, RequestHandler};
1919
use super::{Result, schedule::BackgroundSchedule};
2020
use crate::session::client::Client;
2121
pub(crate) use diagnostics::publish_settings_diagnostics;
22+
pub use requests::{PartialWorkspaceProgress, PartialWorkspaceProgressParams};
2223
use ruff_db::panic::PanicError;
2324

2425
/// Processes a request from the client to the server.

crates/ty_server/src/server/api/requests.rs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -33,3 +33,5 @@ pub(super) use shutdown::ShutdownHandler;
3333
pub(super) use signature_help::SignatureHelpRequestHandler;
3434
pub(super) use workspace_diagnostic::WorkspaceDiagnosticRequestHandler;
3535
pub(super) use workspace_symbols::WorkspaceSymbolRequestHandler;
36+
37+
pub use workspace_diagnostic::{PartialWorkspaceProgress, PartialWorkspaceProgressParams};

0 commit comments

Comments
 (0)