mirror of
https://github.com/erg-lang/erg.git
synced 2025-08-04 18:58:30 +00:00
perf(els): improve diagnostics speed
This commit is contained in:
parent
93305f2081
commit
ae039c9f39
6 changed files with 115 additions and 38 deletions
|
@ -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);
|
||||
},
|
||||
|
|
|
@ -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(×tamps) {
|
||||
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(());
|
||||
}
|
||||
|
|
|
@ -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
|
||||
}
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue