perf(els): improve diagnostics speed

This commit is contained in:
Shunsuke Shibayama 2024-09-18 14:17:16 +09:00
parent 93305f2081
commit ae039c9f39
6 changed files with 115 additions and 38 deletions

View file

@ -6,9 +6,17 @@ use std::sync::mpsc::Receiver;
use std::thread::sleep;
use std::time::Duration;
use lsp_types::{
ConfigurationParams, Diagnostic, DiagnosticSeverity, NumberOrString, Position, ProgressParams,
ProgressParamsValue, PublishDiagnosticsParams, Range, Url, WorkDoneProgress,
WorkDoneProgressBegin, WorkDoneProgressCreateParams, WorkDoneProgressEnd,
};
use serde_json::json;
use erg_common::consts::PYTHON_MODE;
use erg_common::dict::Dict;
use erg_common::pathutil::{project_entry_dir_of, project_entry_file_of};
use erg_common::pathutil::{project_entry_dir_of, project_entry_file_of, NormalizedPathBuf};
use erg_common::set::Set;
use erg_common::spawn::{safe_yield, spawn_new_thread};
use erg_common::style::*;
use erg_common::{fn_name, lsp_log};
@ -19,13 +27,6 @@ use erg_compiler::erg_parser::error::IncompleteArtifact;
use erg_compiler::erg_parser::parse::Parsable;
use erg_compiler::error::CompileErrors;
use lsp_types::{
ConfigurationParams, Diagnostic, DiagnosticSeverity, NumberOrString, Position, ProgressParams,
ProgressParamsValue, PublishDiagnosticsParams, Range, Url, WorkDoneProgress,
WorkDoneProgressBegin, WorkDoneProgressCreateParams, WorkDoneProgressEnd,
};
use serde_json::json;
use crate::_log;
use crate::channels::WorkerMessage;
use crate::diff::{ASTDiff, HIRDiff};
@ -95,16 +96,20 @@ impl<Checker: BuildRunnable, Parser: Parsable> Server<Checker, Parser> {
return Ok(());
}
// self.clear_cache(&uri);
self.check_file(uri, code)
let mut checked = Set::new();
self.check_file(uri, code, &mut checked)?;
self.send_empty_diagnostics(checked)?;
Ok(())
}
pub(crate) fn check_file(
&mut self,
uri: NormalizedUrl,
code: impl Into<String>,
checked: &mut Set<NormalizedUrl>,
) -> ELSResult<()> {
_log!(self, "checking {uri}");
if self.file_cache.editing.borrow().contains(&uri) {
if self.file_cache.editing.borrow().contains(&uri) || checked.contains(&uri) {
_log!(self, "skipped: {uri}");
return Ok(());
}
@ -170,6 +175,15 @@ impl<Checker: BuildRunnable, Parser: Parsable> Server<Checker, Parser> {
(artifact, CheckStatus::Failed)
}
};
checked.insert(uri.clone());
if let Some(files) = artifact.object.as_ref().map(|art| &art.dependencies) {
checked.extend(
files
.iter()
.cloned()
.filter_map(|file| NormalizedUrl::from_file_path(file).ok()),
);
}
let ast = match self.build_ast(&uri) {
Ok(ast) => Some(ast),
Err(BuildASTError::ParseError(err)) => err.ast,
@ -192,13 +206,26 @@ impl<Checker: BuildRunnable, Parser: Parsable> Server<Checker, Parser> {
for dep in dependents {
// _log!(self, "dep: {dep}");
let code = self.file_cache.get_entire_code(&dep)?.to_string();
self.check_file(dep, code)?;
self.check_file(dep, code, checked)?;
}
self.shared.errors.extend(artifact.errors);
self.shared.warns.extend(artifact.warns);
Ok(())
}
pub(crate) fn send_empty_diagnostics(&self, checked: Set<NormalizedUrl>) -> ELSResult<()> {
for checked in checked {
let Ok(path) = checked.to_file_path() else {
continue;
};
let path = NormalizedPathBuf::from(path);
if self.shared.errors.get(&path).is_empty() && self.shared.warns.get(&path).is_empty() {
self.send_diagnostics(checked.raw(), vec![])?;
}
}
Ok(())
}
// TODO: reset mutable dependent types
pub(crate) fn quick_check_file(&mut self, uri: NormalizedUrl) -> ELSResult<()> {
if self.file_cache.editing.borrow().contains(&uri) {
@ -353,7 +380,9 @@ impl<Checker: BuildRunnable, Parser: Parsable> Server<Checker, Parser> {
};
if latest_ver != ver {
if let Ok(code) = _self.file_cache.get_entire_code(&uri) {
let _ = _self.check_file(uri.clone(), code);
let mut checked = Set::new();
let _ = _self.check_file(uri.clone(), code, &mut checked);
_self.send_empty_diagnostics(checked).unwrap();
file_vers.insert(uri, latest_ver);
}
}
@ -473,7 +502,9 @@ impl<Checker: BuildRunnable, Parser: Parsable> Server<Checker, Parser> {
}))
.unwrap();
if let Ok(code) = _self.file_cache.get_entire_code(&main_uri) {
let _ = _self.check_file(main_uri.clone(), code);
let mut checked = Set::new();
let _ = _self.check_file(main_uri.clone(), code, &mut checked);
_self.send_empty_diagnostics(checked).unwrap();
}
work_done!(token, uris);
},

View file

@ -1,26 +1,28 @@
use std::collections::HashMap;
use std::path::PathBuf;
use std::time::SystemTime;
use std::thread::sleep;
use std::time::{Duration, SystemTime};
use erg_common::pathutil::NormalizedPathBuf;
use erg_common::traits::{Locational, Stream};
use erg_compiler::erg_parser::parse::Parsable;
use serde::Deserialize;
use serde_json::json;
use serde_json::Value;
use erg_common::dict::Dict;
use erg_compiler::artifact::BuildRunnable;
use erg_compiler::hir::{Expr, Literal};
use erg_compiler::varinfo::{AbsLocation, VarKind};
use lsp_types::{
DocumentChangeOperation, DocumentChanges, OneOf, OptionalVersionedTextDocumentIdentifier,
RenameFile, RenameFilesParams, RenameParams, ResourceOp, TextDocumentEdit, TextEdit, Url,
WorkspaceEdit,
};
use erg_common::dict::Dict;
use erg_common::pathutil::NormalizedPathBuf;
use erg_common::set::Set;
use erg_common::traits::{Locational, Stream};
use erg_compiler::artifact::BuildRunnable;
use erg_compiler::erg_parser::parse::Parsable;
use erg_compiler::hir::{Expr, Literal};
use erg_compiler::varinfo::{AbsLocation, VarKind};
#[allow(unused_imports)]
use crate::_log;
use crate::server::{ELSResult, RedirectableStdout, Server};
@ -93,14 +95,16 @@ impl<Checker: BuildRunnable, Parser: Parsable> Server<Checker, Parser> {
if self.all_changed(&timestamps) {
break;
}
std::thread::sleep(std::time::Duration::from_millis(50));
sleep(Duration::from_millis(50));
}
let mut checked = Set::new();
// recheck dependencies and finally the file itself
for dep in dependencies {
let code = self.file_cache.get_entire_code(&dep)?.to_string();
self.check_file(dep.clone(), code)?;
self.check_file(dep.clone(), code, &mut checked)?;
self.file_cache.editing.borrow_mut().remove(&dep);
}
self.send_empty_diagnostics(checked)?;
// dependents are checked after changes are committed
return Ok(());
}

View file

@ -13,6 +13,7 @@ use erg_common::consts::PYTHON_MODE;
use erg_common::dict::Dict;
use erg_common::env::erg_path;
use erg_common::pathutil::{project_entry_dir_of, NormalizedPathBuf};
use erg_common::set::Set;
use erg_common::shared::{MappedRwLockReadGuard, Shared};
use erg_common::spawn::{safe_yield, spawn_new_thread};
use erg_common::traits::Stream;
@ -344,6 +345,9 @@ impl<Checker: BuildRunnable, Parser: Parsable> Server<Checker, Parser> {
if let Err(err) = _self.dispatch(msg) {
lsp_log!("error: {err}");
if err.to_string().contains("sending on a closed channel") {
_self
.send_error_info("An error occurred. Restarting...")
.unwrap();
_self.restart();
};
}
@ -371,6 +375,9 @@ impl<Checker: BuildRunnable, Parser: Parsable> Server<Checker, Parser> {
if let Err(err) = _self.dispatch(msg) {
lsp_log!("error: {err}");
if err.to_string().contains("sending on a closed channel") {
_self
.send_error_info("An error occurred again. Restarting...")
.unwrap();
_self.restart();
};
}
@ -882,7 +889,9 @@ impl<Checker: BuildRunnable, Parser: Parsable> Server<Checker, Parser> {
let ver = params.text_document.version;
self.file_cache.update(&uri, code.clone(), Some(ver));
let token = self.start_work_done_progress("checking files ...");
let res = self.check_file(uri, code);
let mut checked = Set::new();
let res = self.check_file(uri.clone(), code, &mut checked);
// self.send_empty_diagnostics(checked)?;
self.stop_work_done_progress(token, "checking done");
res
}

View file

@ -29,7 +29,7 @@ pub struct IncompleteArtifact<Inner = HIR> {
pub warns: CompileErrors,
}
impl fmt::Display for IncompleteArtifact {
impl<I> fmt::Display for IncompleteArtifact<I> {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
if !self.warns.is_empty() {
writeln!(f, "{}", self.warns)?;
@ -38,10 +38,10 @@ impl fmt::Display for IncompleteArtifact {
}
}
impl std::error::Error for IncompleteArtifact {}
impl<I: fmt::Debug> std::error::Error for IncompleteArtifact<I> {}
impl From<CompleteArtifact> for IncompleteArtifact {
fn from(artifact: CompleteArtifact) -> Self {
impl<Inner> From<CompleteArtifact<Inner>> for IncompleteArtifact<Inner> {
fn from(artifact: CompleteArtifact<Inner>) -> Self {
Self {
object: Some(artifact.object),
errors: CompileErrors::empty(),
@ -77,8 +77,8 @@ impl fmt::Display for ErrorArtifact {
impl std::error::Error for ErrorArtifact {}
impl From<IncompleteArtifact> for ErrorArtifact {
fn from(artifact: IncompleteArtifact) -> Self {
impl<Inner> From<IncompleteArtifact<Inner>> for ErrorArtifact {
fn from(artifact: IncompleteArtifact<Inner>) -> Self {
Self {
errors: artifact.errors,
warns: artifact.warns,

View file

@ -22,6 +22,7 @@ use erg_common::io::Input;
#[allow(unused)]
use erg_common::log;
use erg_common::pathutil::{mod_name, project_entry_dir_of, NormalizedPathBuf};
use erg_common::set::Set;
use erg_common::spawn::spawn_new_thread;
use erg_common::str::Str;
use erg_common::traits::{ExitStatus, New, Runnable, Stream};
@ -277,7 +278,7 @@ impl<ASTBuilder: ASTBuildable, HIRBuilder: Buildable> Buildable
&mut self,
ast: AST,
mode: &str,
) -> Result<CompleteArtifact<crate::hir::HIR>, IncompleteArtifact<crate::hir::HIR>> {
) -> Result<CompleteArtifact, IncompleteArtifact> {
self.build_root(ast, mode)
}
fn pop_context(&mut self) -> Option<ModuleContext> {
@ -820,14 +821,29 @@ impl<ASTBuilder: ASTBuildable, HIRBuilder: Buildable>
log!(info "Start to spawn dependencies processes");
let path = NormalizedPathBuf::from(self.cfg.input.path());
let mut graph = self.shared.graph.clone_inner();
self.build_deps_and_module(&path, &mut graph);
let deps = self.build_deps_and_module(&path, &mut graph);
log!(info "All dependencies have started to analyze");
debug_power_assert!(self.asts.len(), ==, 0);
self.finalize();
self.main_builder.build_from_ast(ast, mode)
match self.main_builder.build_from_ast(ast, mode) {
Ok(artifact) => Ok(CompleteArtifact::new(
artifact.object.with_dependencies(deps),
artifact.warns,
)),
Err(artifact) => Err(IncompleteArtifact::new(
artifact.object.map(|hir| hir.with_dependencies(deps)),
artifact.errors,
artifact.warns,
)),
}
}
fn build_deps_and_module(&mut self, path: &NormalizedPathBuf, graph: &mut ModuleGraph) {
fn build_deps_and_module(
&mut self,
path: &NormalizedPathBuf,
graph: &mut ModuleGraph,
) -> Set<NormalizedPathBuf> {
let mut deps = Set::new();
let mut ancestors = graph.ancestors(path).into_vec();
let nmods = ancestors.len();
let pad = nmods.to_string().len();
@ -842,6 +858,7 @@ impl<ASTBuilder: ASTBuildable, HIRBuilder: Buildable>
if graph.ancestors(&ancestor).is_empty() {
graph.remove(&ancestor);
if let Some(entry) = self.asts.remove(&ancestor) {
deps.insert(ancestor.clone());
if print_progress {
let name = ancestor.file_name().unwrap_or_default().to_string_lossy();
let checked = nmods - ancestors.len();
@ -867,8 +884,10 @@ impl<ASTBuilder: ASTBuildable, HIRBuilder: Buildable>
if print_progress {
println!();
}
deps
}
// REVIEW: should return dep files?
fn build_inlined_module(&mut self, path: &NormalizedPathBuf, graph: &mut ModuleGraph) {
if self.shared.get_module(path).is_some() {
// do nothing

View file

@ -6,6 +6,7 @@ use erg_common::dict::Dict as HashMap;
use erg_common::error::Location;
#[allow(unused_imports)]
use erg_common::log;
use erg_common::pathutil::NormalizedPathBuf;
use erg_common::set::Set as HashSet;
use erg_common::traits::{Locational, NestedDisplay, NoTypeDisplay, Stream};
use erg_common::{
@ -3216,6 +3217,7 @@ impl Module {
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
pub struct HIR {
pub name: Str,
pub dependencies: HashSet<NormalizedPathBuf>,
pub module: Module,
}
@ -3229,13 +3231,25 @@ impl Default for HIR {
fn default() -> Self {
Self {
name: Str::ever("<module>"),
dependencies: HashSet::default(),
module: Module(vec![]),
}
}
}
impl HIR {
pub const fn new(name: Str, module: Module) -> Self {
Self { name, module }
pub fn new(name: Str, module: Module) -> Self {
Self {
name,
dependencies: HashSet::default(),
module,
}
}
pub fn with_dependencies(self, deps: HashSet<NormalizedPathBuf>) -> Self {
Self {
dependencies: self.dependencies.concat(deps),
..self
}
}
}