diff --git a/crates/tinymist-query/src/analysis.rs b/crates/tinymist-query/src/analysis.rs index 94f3637d..92447ece 100644 --- a/crates/tinymist-query/src/analysis.rs +++ b/crates/tinymist-query/src/analysis.rs @@ -1,7 +1,6 @@ //! Semantic static and dynamic analysis of the source code. mod bib; -use std::path::Path; pub(crate) use bib::*; pub mod call; @@ -24,11 +23,6 @@ pub mod signature; pub use signature::*; pub mod semantic_tokens; pub use semantic_tokens::*; -use tinymist_std::ImmutPath; -use tinymist_world::vfs::WorkspaceResolver; -use tinymist_world::WorldDeps; -use typst::syntax::Source; -use typst::World; mod post_tyck; mod tyck; pub(crate) use crate::ty::*; @@ -39,13 +33,21 @@ mod prelude; mod global; pub use global::*; +use std::path::Path; +use std::sync::Arc; + use ecow::{eco_format, EcoVec}; use lsp_types::Url; +use tinymist_project::LspComputeGraph; +use tinymist_std::{bail, ImmutPath, Result}; +use tinymist_world::vfs::WorkspaceResolver; +use tinymist_world::{EntryReader, TaskInputs, WorldDeps}; use typst::diag::{FileError, FileResult}; use typst::foundations::{Func, Value}; -use typst::syntax::FileId; +use typst::syntax::{FileId, Source}; +use typst::World; -use crate::path_res_to_url; +use crate::{path_res_to_url, CompilerQueryResponse, SemanticRequest, StatefulRequest}; pub(crate) trait ToFunc { fn to_func(&self) -> Option; @@ -124,6 +126,64 @@ impl LspWorldExt for tinymist_project::LspWorld { } } +/// A snapshot for LSP queries. +pub struct LspQuerySnapshot { + /// The using snapshot. + pub snap: LspComputeGraph, + /// The global shared analysis data. + analysis: Arc, + /// The revision lock for the analysis (cache). + rev_lock: AnalysisRevLock, +} + +impl std::ops::Deref for LspQuerySnapshot { + type Target = LspComputeGraph; + + fn deref(&self) -> &Self::Target { + &self.snap + } +} + +impl LspQuerySnapshot { + /// Runs a query for another task. + pub fn task(mut self, inputs: TaskInputs) -> Self { + self.snap = self.snap.task(inputs); + self + } + + /// Runs a stateful query. + pub fn run_stateful( + self, + query: T, + wrapper: fn(Option) -> CompilerQueryResponse, + ) -> Result { + let graph = self.snap.clone(); + self.run_analysis(|ctx| query.request(ctx, graph)) + .map(wrapper) + } + + /// Runs a semantic query. + pub fn run_semantic( + self, + query: T, + wrapper: fn(Option) -> CompilerQueryResponse, + ) -> Result { + self.run_analysis(|ctx| query.request(ctx)).map(wrapper) + } + + /// Runs a query. + pub fn run_analysis(self, f: impl FnOnce(&mut LocalContextGuard) -> T) -> Result { + let world = self.snap.world().clone(); + let Some(..) = world.main_id() else { + log::error!("Project: main file is not set"); + bail!("main file is not set"); + }; + + let mut ctx = self.analysis.enter_(world, self.rev_lock); + Ok(f(&mut ctx)) + } +} + #[cfg(test)] mod matcher_tests { @@ -611,9 +671,7 @@ mod lint_tests { let source = ctx.source_by_path(&path).unwrap(); let result = tinymist_lint::lint_source(&source); - let result = - crate::diagnostics::CheckDocWorker::new(&ctx.world, ctx.position_encoding()) - .convert_all(result.iter()); + let result = crate::diagnostics::DiagWorker::new(ctx).convert_all(result.iter()); let result = result .into_iter() .map(|(k, v)| (file_path_(&k), v)) diff --git a/crates/tinymist-query/src/analysis/definition.rs b/crates/tinymist-query/src/analysis/definition.rs index bae64f59..019ce409 100644 --- a/crates/tinymist-query/src/analysis/definition.rs +++ b/crates/tinymist-query/src/analysis/definition.rs @@ -354,7 +354,7 @@ fn value_to_def(value: Value, name: impl FnOnce() -> Option>) -> O } struct DefResolver { - ei: Arc, + ei: ExprInfo, } impl DefResolver { diff --git a/crates/tinymist-query/src/analysis/global.rs b/crates/tinymist-query/src/analysis/global.rs index e2387be0..fb59a078 100644 --- a/crates/tinymist-query/src/analysis/global.rs +++ b/crates/tinymist-query/src/analysis/global.rs @@ -11,7 +11,7 @@ use rustc_hash::FxHashMap; use tinymist_analysis::stats::AllocStats; use tinymist_analysis::ty::term_value; use tinymist_analysis::{analyze_expr_, analyze_import_}; -use tinymist_project::LspWorld; +use tinymist_project::{LspComputeGraph, LspWorld}; use tinymist_std::hash::{hash128, FxDashMap}; use tinymist_std::typst::TypstDocument; use tinymist_world::debug_loc::DataSource; @@ -24,6 +24,7 @@ use typst::syntax::package::{PackageManifest, PackageSpec}; use typst::syntax::{Span, VirtualPath}; use typst_shim::eval::{eval_compat, Eval}; +use super::{LspQuerySnapshot, TypeEnv}; use crate::adt::revision::{RevisionLock, RevisionManager, RevisionManagerLike, RevisionSlot}; use crate::analysis::prelude::*; use crate::analysis::{ @@ -42,8 +43,6 @@ use crate::{ ColorTheme, CompilerQueryRequest, LspPosition, LspRange, LspWorldExt, PositionEncoding, }; -use super::TypeEnv; - macro_rules! interned_str { ($name:ident, $value:expr) => { static $name: LazyLock> = LazyLock::new(|| $value.into()); @@ -80,13 +79,13 @@ pub struct Analysis { } impl Analysis { - /// Get a snapshot of the analysis data. - pub fn snapshot(&self, world: LspWorld) -> LocalContextGuard { - self.snapshot_(world, self.lock_revision(None)) + /// Enters the analysis context. + pub fn enter(&self, world: LspWorld) -> LocalContextGuard { + self.enter_(world, self.lock_revision(None)) } - /// Get a snapshot of the analysis data. - pub fn snapshot_(&self, world: LspWorld, mut lg: AnalysisRevLock) -> LocalContextGuard { + /// Enters the analysis context. + pub(crate) fn enter_(&self, world: LspWorld, mut lg: AnalysisRevLock) -> LocalContextGuard { let lifetime = self.caches.lifetime.fetch_add(1, Ordering::SeqCst); let slot = self .analysis_rev_cache @@ -94,7 +93,7 @@ impl Analysis { .find_revision(world.revision(), &lg); let tokens = lg.tokens.take(); LocalContextGuard { - rev_lock: lg, + _rev_lock: lg, local: LocalContext { tokens, caches: AnalysisLocalCaches::default(), @@ -108,7 +107,21 @@ impl Analysis { } } - /// Lock the revision in *main thread*. + /// Gets a snapshot for language queries. + pub fn query_snapshot( + self: Arc, + snap: LspComputeGraph, + req: Option<&CompilerQueryRequest>, + ) -> LspQuerySnapshot { + let rev_lock = self.lock_revision(req); + LspQuerySnapshot { + snap, + analysis: self, + rev_lock, + } + } + + /// Locks the revision in *main thread*. #[must_use] pub fn lock_revision(&self, req: Option<&CompilerQueryRequest>) -> AnalysisRevLock { let mut grid = self.analysis_rev_cache.lock(); @@ -209,7 +222,7 @@ pub struct LocalContextGuard { /// The guarded local context pub local: LocalContext, /// The revision lock - pub rev_lock: AnalysisRevLock, + _rev_lock: AnalysisRevLock, } impl Deref for LocalContextGuard { @@ -430,12 +443,12 @@ impl LocalContext { } /// Get the expression information of a source file. - pub(crate) fn expr_stage_by_id(&mut self, fid: TypstFileId) -> Option> { + pub(crate) fn expr_stage_by_id(&mut self, fid: TypstFileId) -> Option { Some(self.expr_stage(&self.source_by_id(fid).ok()?)) } /// Get the expression information of a source file. - pub(crate) fn expr_stage(&mut self, source: &Source) -> Arc { + pub(crate) fn expr_stage(&mut self, source: &Source) -> ExprInfo { let id = source.id(); let cache = &self.caches.modules.entry(id).or_default().expr_stage; cache.get_or_init(|| self.shared.expr_stage(source)).clone() @@ -692,12 +705,12 @@ impl SharedContext { } /// Get the expression information of a source file. - pub(crate) fn expr_stage_by_id(self: &Arc, fid: TypstFileId) -> Option> { + pub(crate) fn expr_stage_by_id(self: &Arc, fid: TypstFileId) -> Option { Some(self.expr_stage(&self.source_by_id(fid).ok()?)) } /// Get the expression information of a source file. - pub(crate) fn expr_stage(self: &Arc, source: &Source) -> Arc { + pub(crate) fn expr_stage(self: &Arc, source: &Source) -> ExprInfo { let mut route = ExprRoute::default(); self.expr_stage_(source, &mut route) } @@ -707,7 +720,7 @@ impl SharedContext { self: &Arc, source: &Source, route: &mut ExprRoute, - ) -> Arc { + ) -> ExprInfo { use crate::syntax::expr_of; let guard = self.query_stat(source.id(), "expr_stage"); self.slot.expr_stage.compute(hash128(&source), |prev| { @@ -1155,7 +1168,7 @@ pub struct AnalysisLocalCaches { /// change. #[derive(Default)] pub struct ModuleAnalysisLocalCache { - expr_stage: OnceLock>, + expr_stage: OnceLock, type_check: OnceLock>, } @@ -1243,7 +1256,7 @@ impl Drop for AnalysisRevLock { #[derive(Default, Clone)] struct AnalysisRevSlot { revision: usize, - expr_stage: IncrCacheMap>, + expr_stage: IncrCacheMap, type_check: IncrCacheMap>, } diff --git a/crates/tinymist-query/src/analysis/semantic_tokens.rs b/crates/tinymist-query/src/analysis/semantic_tokens.rs index a7c73809..d87b5a9f 100644 --- a/crates/tinymist-query/src/analysis/semantic_tokens.rs +++ b/crates/tinymist-query/src/analysis/semantic_tokens.rs @@ -309,7 +309,7 @@ pub(crate) struct Tokenizer { pos_offset: usize, output: Vec, source: Source, - ei: Arc, + ei: ExprInfo, encoding: PositionEncoding, allow_multiline_token: bool, @@ -320,7 +320,7 @@ pub(crate) struct Tokenizer { impl Tokenizer { pub fn new( source: Source, - ei: Arc, + ei: ExprInfo, allow_multiline_token: bool, encoding: PositionEncoding, ) -> Self { diff --git a/crates/tinymist-query/src/analysis/tyck.rs b/crates/tinymist-query/src/analysis/tyck.rs index c4cb9695..517fd7c9 100644 --- a/crates/tinymist-query/src/analysis/tyck.rs +++ b/crates/tinymist-query/src/analysis/tyck.rs @@ -25,13 +25,13 @@ pub(crate) use select::*; #[derive(Default)] pub struct TypeEnv { visiting: FxHashMap>, - exprs: FxHashMap>>, + exprs: FxHashMap>, } /// Type checking at the source unit level. pub(crate) fn type_check( ctx: Arc, - ei: Arc, + ei: ExprInfo, env: &mut TypeEnv, ) -> Arc { let mut info = TypeInfo::default(); @@ -82,7 +82,7 @@ type CallCacheDesc = ( pub(crate) struct TypeChecker<'a> { ctx: Arc, - ei: Arc, + ei: ExprInfo, info: TypeInfo, module_exports: FxHashMap<(TypstFileId, Interned), OnceLock>>, diff --git a/crates/tinymist-query/src/check.rs b/crates/tinymist-query/src/check.rs new file mode 100644 index 00000000..f15ecadb --- /dev/null +++ b/crates/tinymist-query/src/check.rs @@ -0,0 +1,19 @@ +use tinymist_project::LspCompiledArtifact; + +use crate::{prelude::*, DiagWorker, DiagnosticsMap, SemanticRequest}; + +/// A request to check the document for errors and lints. +#[derive(Clone)] +pub struct CheckRequest { + /// The compilation result of the document. + pub snap: LspCompiledArtifact, +} + +impl SemanticRequest for CheckRequest { + type Response = DiagnosticsMap; + + fn request(self, ctx: &mut LocalContext) -> Option { + let worker = DiagWorker::new(ctx); + Some(worker.check().convert_all(self.snap.diagnostics())) + } +} diff --git a/crates/tinymist-query/src/diagnostics.rs b/crates/tinymist-query/src/diagnostics.rs index 7a302b02..dbdc4dd4 100644 --- a/crates/tinymist-query/src/diagnostics.rs +++ b/crates/tinymist-query/src/diagnostics.rs @@ -4,7 +4,7 @@ use tinymist_project::LspWorld; use tinymist_world::vfs::WorkspaceResolver; use typst::{diag::SourceDiagnostic, syntax::Span}; -use crate::{prelude::*, LspWorldExt}; +use crate::{analysis::Analysis, prelude::*, LspWorldExt}; use regex::RegexSet; @@ -16,52 +16,44 @@ type TypstSeverity = typst::diag::Severity; /// Converts a list of Typst diagnostics to LSP diagnostics, /// with potential refinements on the error messages. -pub fn check_doc<'a>( +pub fn convert_diagnostics<'a>( world: &LspWorld, errors: impl IntoIterator, position_encoding: PositionEncoding, ) -> DiagnosticsMap { - CheckDocWorker::new(world, position_encoding) - .check() - .convert_all(errors) + let analysis = Analysis { + position_encoding, + ..Analysis::default() + }; + let mut ctx = analysis.enter(world.clone()); + DiagWorker::new(&mut ctx).convert_all(errors) } -/// Context for converting Typst diagnostics to LSP diagnostics. -pub(crate) struct CheckDocWorker<'a> { +/// The worker for collecting diagnostics. +pub(crate) struct DiagWorker<'a> { /// The world surface for Typst compiler. - pub world: &'a LspWorld, - /// The position encoding for the source. - pub position_encoding: PositionEncoding, + pub ctx: &'a mut LocalContext, /// Results pub results: DiagnosticsMap, } -impl std::ops::Deref for CheckDocWorker<'_> { - type Target = LspWorld; - - fn deref(&self) -> &Self::Target { - self.world - } -} - -impl<'w> CheckDocWorker<'w> { +impl<'w> DiagWorker<'w> { /// Creates a new `CheckDocWorker` instance. - pub fn new(world: &'w LspWorld, position_encoding: PositionEncoding) -> Self { + pub fn new(ctx: &'w mut LocalContext) -> Self { Self { - world, - position_encoding, + ctx, results: DiagnosticsMap::default(), } } /// Runs code check on the document. pub fn check(mut self) -> Self { - for dep in self.world.depended_files() { + for dep in self.ctx.world.depended_files() { if WorkspaceResolver::is_package_file(dep) { continue; } - let Ok(source) = self.world.source(dep) else { + let Ok(source) = self.ctx.world.source(dep) else { continue; }; let res = lint_source(&source); @@ -89,7 +81,7 @@ impl<'w> CheckDocWorker<'w> { /// Converts a list of Typst diagnostics to LSP diagnostics. pub fn handle(&mut self, diag: &TypstDiagnostic) { - match convert_diagnostic(self, diag) { + match self.convert_diagnostic(diag) { Ok((uri, diagnostic)) => { self.results.entry(uri).or_default().push(diagnostic); } @@ -98,121 +90,92 @@ impl<'w> CheckDocWorker<'w> { } } } -} -fn convert_diagnostic( - ctx: &CheckDocWorker, - typst_diagnostic: &TypstDiagnostic, -) -> anyhow::Result<(Url, Diagnostic)> { - let typst_diagnostic = { - let mut diag = Cow::Borrowed(typst_diagnostic); + fn convert_diagnostic( + &self, + typst_diagnostic: &TypstDiagnostic, + ) -> anyhow::Result<(Url, Diagnostic)> { + let typst_diagnostic = { + let mut diag = Cow::Borrowed(typst_diagnostic); - // Extend more refiners here by adding their instances. - let refiners: &[&dyn DiagnosticRefiner] = - &[&DeprecationRefiner::<13> {}, &OutOfRootHintRefiner {}]; + // Extend more refiners here by adding their instances. + let refiners: &[&dyn DiagnosticRefiner] = + &[&DeprecationRefiner::<13> {}, &OutOfRootHintRefiner {}]; - // NOTE: It would be nice to have caching here. - for refiner in refiners { - if refiner.matches(&diag) { - diag = Cow::Owned(refiner.refine(diag.into_owned())); + // NOTE: It would be nice to have caching here. + for refiner in refiners { + if refiner.matches(&diag) { + diag = Cow::Owned(refiner.refine(diag.into_owned())); + } } - } - diag - }; + diag + }; - let (id, span) = diagnostic_span_id(ctx, &typst_diagnostic); - let uri = ctx.uri_for_id(id)?; - let source = ctx.source(id)?; - let lsp_range = diagnostic_range(&source, span, ctx.position_encoding); + let (id, span) = self.diagnostic_span_id(&typst_diagnostic); + let uri = self.ctx.uri_for_id(id)?; + let source = self.ctx.source_by_id(id)?; + let lsp_range = self.diagnostic_range(&source, span); - let lsp_severity = diagnostic_severity(typst_diagnostic.severity); - let lsp_message = diagnostic_message(&typst_diagnostic); + let lsp_severity = diagnostic_severity(typst_diagnostic.severity); + let lsp_message = diagnostic_message(&typst_diagnostic); - let tracepoints = - diagnostic_related_information(ctx, &typst_diagnostic, ctx.position_encoding)?; + let diagnostic = Diagnostic { + range: lsp_range, + severity: Some(lsp_severity), + message: lsp_message, + source: Some("typst".to_owned()), + related_information: (!typst_diagnostic.trace.is_empty()).then(|| { + typst_diagnostic + .trace + .iter() + .flat_map(|tracepoint| self.to_related_info(tracepoint)) + .collect() + }), + ..Default::default() + }; - let diagnostic = Diagnostic { - range: lsp_range, - severity: Some(lsp_severity), - message: lsp_message, - source: Some("typst".to_owned()), - related_information: Some(tracepoints), - ..Default::default() - }; - - Ok((uri, diagnostic)) -} - -fn tracepoint_to_relatedinformation( - ctx: &CheckDocWorker, - tracepoint: &Spanned, - position_encoding: PositionEncoding, -) -> anyhow::Result> { - if let Some(id) = tracepoint.span.id() { - let uri = ctx.uri_for_id(id)?; - let source = ctx.source(id)?; - - if let Some(typst_range) = source.range(tracepoint.span) { - let lsp_range = to_lsp_range(typst_range, &source, position_encoding); - - return Ok(Some(DiagnosticRelatedInformation { - location: LspLocation { - uri, - range: lsp_range, - }, - message: tracepoint.v.to_string(), - })); - } + Ok((uri, diagnostic)) } - Ok(None) -} + fn to_related_info( + &self, + tracepoint: &Spanned, + ) -> Option { + let id = tracepoint.span.id()?; + // todo: expensive uri_for_id + let uri = self.ctx.uri_for_id(id).ok()?; + let source = self.ctx.source_by_id(id).ok()?; -fn diagnostic_related_information( - project: &CheckDocWorker, - typst_diagnostic: &TypstDiagnostic, - position_encoding: PositionEncoding, -) -> anyhow::Result> { - let mut tracepoints = vec![]; + let typst_range = source.range(tracepoint.span)?; + let lsp_range = self.ctx.to_lsp_range(typst_range, &source); - for tracepoint in &typst_diagnostic.trace { - if let Some(info) = - tracepoint_to_relatedinformation(project, tracepoint, position_encoding)? - { - tracepoints.push(info); - } + Some(DiagnosticRelatedInformation { + location: LspLocation { + uri, + range: lsp_range, + }, + message: tracepoint.v.to_string(), + }) } - Ok(tracepoints) -} + fn diagnostic_span_id(&self, typst_diagnostic: &TypstDiagnostic) -> (TypstFileId, Span) { + iter::once(typst_diagnostic.span) + .chain(typst_diagnostic.trace.iter().map(|trace| trace.span)) + .find_map(|span| Some((span.id()?, span))) + .unwrap_or_else(|| (self.ctx.world.main(), Span::detached())) + } -fn diagnostic_span_id( - ctx: &CheckDocWorker, - typst_diagnostic: &TypstDiagnostic, -) -> (TypstFileId, Span) { - iter::once(typst_diagnostic.span) - .chain(typst_diagnostic.trace.iter().map(|trace| trace.span)) - .find_map(|span| Some((span.id()?, span))) - .unwrap_or_else(|| (ctx.main(), Span::detached())) -} - -fn diagnostic_range( - source: &Source, - typst_span: Span, - position_encoding: PositionEncoding, -) -> LspRange { - // Due to nvaner/typst-lsp#241 and maybe typst/typst#2035, we sometimes fail to - // find the span. In that case, we use a default span as a better - // alternative to panicking. - // - // This may have been fixed after Typst 0.7.0, but it's still nice to avoid - // panics in case something similar reappears. - match source.find(typst_span) { - Some(node) => { - let typst_range = node.range(); - to_lsp_range(typst_range, source, position_encoding) + fn diagnostic_range(&self, source: &Source, typst_span: Span) -> LspRange { + // Due to nvaner/typst-lsp#241 and maybe typst/typst#2035, we sometimes fail to + // find the span. In that case, we use a default span as a better + // alternative to panicking. + // + // This may have been fixed after Typst 0.7.0, but it's still nice to avoid + // panics in case something similar reappears. + match source.find(typst_span) { + Some(node) => self.ctx.to_lsp_range(node.range(), source), + None => LspRange::new(LspPosition::new(0, 0), LspPosition::new(0, 0)), } - None => LspRange::new(LspPosition::new(0, 0), LspPosition::new(0, 0)), } } diff --git a/crates/tinymist-query/src/docs/module.rs b/crates/tinymist-query/src/docs/module.rs index d42be28c..78934843 100644 --- a/crates/tinymist-query/src/docs/module.rs +++ b/crates/tinymist-query/src/docs/module.rs @@ -1,7 +1,6 @@ //! Module documentation. use std::collections::HashMap; -use std::sync::Arc; use ecow::{eco_vec, EcoString, EcoVec}; use itertools::Itertools; @@ -113,7 +112,7 @@ struct ScanDefCtx<'a> { } impl ScanDefCtx<'_> { - fn defs(&mut self, paths: EcoVec<&str>, ei: Arc) -> DefInfo { + fn defs(&mut self, paths: EcoVec<&str>, ei: ExprInfo) -> DefInfo { let name = { let stem = ei.fid.vpath().as_rooted_path().file_stem(); stem.and_then(|s| Some(Interned::new_str(s.to_str()?))) diff --git a/crates/tinymist-query/src/fixtures/lint/snaps/test@if_set.typ.snap b/crates/tinymist-query/src/fixtures/lint/snaps/test@if_set.typ.snap index b79641d1..996dfa55 100644 --- a/crates/tinymist-query/src/fixtures/lint/snaps/test@if_set.typ.snap +++ b/crates/tinymist-query/src/fixtures/lint/snaps/test@if_set.typ.snap @@ -8,7 +8,6 @@ input_file: crates/tinymist-query/src/fixtures/lint/if_set.typ { "message": "This set statement doesn't take effect.\nHint: consider changing parent to `set text(red) if (false)`", "range": "1:2:1:15", - "relatedInformation": [], "severity": 2, "source": "typst" } diff --git a/crates/tinymist-query/src/fixtures/lint/snaps/test@if_show.typ.snap b/crates/tinymist-query/src/fixtures/lint/snaps/test@if_show.typ.snap index 17e9809a..476d9b76 100644 --- a/crates/tinymist-query/src/fixtures/lint/snaps/test@if_show.typ.snap +++ b/crates/tinymist-query/src/fixtures/lint/snaps/test@if_show.typ.snap @@ -8,7 +8,6 @@ input_file: crates/tinymist-query/src/fixtures/lint/if_show.typ { "message": "This show statement doesn't take effect.\nHint: consider changing parent to `show : if (false) { .. }`", "range": "1:2:1:17", - "relatedInformation": [], "severity": 2, "source": "typst" } diff --git a/crates/tinymist-query/src/fixtures/lint/snaps/test@show_set.typ.snap b/crates/tinymist-query/src/fixtures/lint/snaps/test@show_set.typ.snap index 40085edf..a3a7b23f 100644 --- a/crates/tinymist-query/src/fixtures/lint/snaps/test@show_set.typ.snap +++ b/crates/tinymist-query/src/fixtures/lint/snaps/test@show_set.typ.snap @@ -8,7 +8,6 @@ input_file: crates/tinymist-query/src/fixtures/lint/show_set.typ { "message": "This set statement doesn't take effect.\nHint: consider changing parent to `show : set text(red)`", "range": "1:2:1:15", - "relatedInformation": [], "severity": 2, "source": "typst" } diff --git a/crates/tinymist-query/src/fixtures/lint/snaps/test@show_set2.typ.snap b/crates/tinymist-query/src/fixtures/lint/snaps/test@show_set2.typ.snap index 54932b5c..4b7adcce 100644 --- a/crates/tinymist-query/src/fixtures/lint/snaps/test@show_set2.typ.snap +++ b/crates/tinymist-query/src/fixtures/lint/snaps/test@show_set2.typ.snap @@ -8,7 +8,6 @@ input_file: crates/tinymist-query/src/fixtures/lint/show_set2.typ { "message": "This set statement doesn't take effect.\nHint: consider changing parent to `show raw: set text(red)`", "range": "1:2:1:15", - "relatedInformation": [], "severity": 2, "source": "typst" } diff --git a/crates/tinymist-query/src/lib.rs b/crates/tinymist-query/src/lib.rs index 0ad7de5c..19a7555b 100644 --- a/crates/tinymist-query/src/lib.rs +++ b/crates/tinymist-query/src/lib.rs @@ -12,6 +12,7 @@ pub use completion::{CompletionRequest, PostfixSnippet}; pub use typlite::ColorTheme; pub use upstream::with_vm; +pub use check::*; pub use code_action::*; pub use code_context::*; pub use code_lens::*; @@ -55,6 +56,7 @@ mod adt; mod lsp_typst_boundary; mod prelude; +mod check; mod code_action; mod code_context; mod code_lens; diff --git a/crates/tinymist-query/src/syntax/expr.rs b/crates/tinymist-query/src/syntax/expr.rs index 82cfb285..e2dc5ce6 100644 --- a/crates/tinymist-query/src/syntax/expr.rs +++ b/crates/tinymist-query/src/syntax/expr.rs @@ -32,8 +32,8 @@ pub(crate) fn expr_of( source: Source, route: &mut ExprRoute, guard: QueryStatGuard, - prev: Option>, -) -> Arc { + prev: Option, +) -> ExprInfo { crate::log_debug_ct!("expr_of: {:?}", source.id()); route.insert(source.id(), None); @@ -76,8 +76,18 @@ pub(crate) fn expr_of( let docstrings_base = Arc::new(Mutex::new(FxHashMap::default())); let docstrings = docstrings_base.clone(); - let exprs_base = Arc::new(Mutex::new(FxHashMap::default())); - let exprs = exprs_base.clone(); + let exprs_base: Arc< + parking_lot::lock_api::Mutex< + parking_lot::RawMutex, + HashMap, + >, + > = Arc::new(Mutex::new(FxHashMap::default())); + let exprs: Arc< + parking_lot::lock_api::Mutex< + parking_lot::RawMutex, + HashMap, + >, + > = exprs_base.clone(); let imports_base = Arc::new(Mutex::new(FxHashMap::default())); let imports = imports_base.clone(); @@ -120,7 +130,7 @@ pub(crate) fn expr_of( (root_scope, root) }; - let info = ExprInfo { + let info = ExprInfoRepr { fid: source.id(), revision, source: source.clone(), @@ -135,11 +145,22 @@ pub(crate) fn expr_of( crate::log_debug_ct!("expr_of end {:?}", source.id()); route.remove(&info.fid); - Arc::new(info) + ExprInfo(Arc::new(LazyHash::new(info))) +} + +#[derive(Debug, Clone, Hash)] +pub struct ExprInfo(Arc>); + +impl Deref for ExprInfo { + type Target = Arc>; + + fn deref(&self) -> &Self::Target { + &self.0 + } } #[derive(Debug)] -pub struct ExprInfo { +pub struct ExprInfoRepr { pub fid: TypstFileId, pub revision: usize, pub source: Source, @@ -152,19 +173,22 @@ pub struct ExprInfo { pub root: Expr, } -impl std::hash::Hash for ExprInfo { +impl std::hash::Hash for ExprInfoRepr { fn hash(&self, state: &mut H) { self.revision.hash(state); self.source.hash(state); self.exports.hash(state); self.root.hash(state); + let mut resolves = self.resolves.iter().collect::>(); + resolves.sort_by_key(|(fid, _)| fid.into_raw()); + resolves.hash(state); let mut imports = self.imports.iter().collect::>(); imports.sort_by_key(|(fid, _)| *fid); imports.hash(state); } } -impl ExprInfo { +impl ExprInfoRepr { pub fn get_def(&self, decl: &Interned) -> Option { if decl.is_def() { return Some(Expr::Decl(decl.clone())); diff --git a/crates/tinymist-query/src/tests.rs b/crates/tinymist-query/src/tests.rs index 80f3f4cb..933f7c70 100644 --- a/crates/tinymist-query/src/tests.rs +++ b/crates/tinymist-query/src/tests.rs @@ -90,7 +90,7 @@ pub fn run_with_ctx( }, ..Analysis::default() }) - .snapshot(world); + .enter(world); ctx.test_package_list(|| { vec![( diff --git a/crates/tinymist/src/project.rs b/crates/tinymist/src/project.rs index 35cb374b..8330e1ca 100644 --- a/crates/tinymist/src/project.rs +++ b/crates/tinymist/src/project.rs @@ -28,10 +28,9 @@ use parking_lot::Mutex; use reflexo::{hash::FxHashMap, path::unix_slash}; use sync_ls::{LspClient, TypedLspClient}; use tinymist_project::vfs::{FileChangeSet, MemoryEvent}; +use tinymist_query::analysis::{Analysis, LspQuerySnapshot, PeriscopeProvider}; use tinymist_query::{ - analysis::{Analysis, AnalysisRevLock, LocalContextGuard, PeriscopeProvider}, - CompilerQueryRequest, CompilerQueryResponse, DiagnosticsMap, LocalContext, SemanticRequest, - StatefulRequest, + CheckRequest, CompilerQueryRequest, DiagnosticsMap, LocalContext, SemanticRequest, }; use tinymist_render::PeriscopeRenderer; use tinymist_std::{error::prelude::*, ImmutPath}; @@ -303,14 +302,7 @@ impl ProjectState { /// Snapshot the compiler thread for language queries pub fn query_snapshot(&mut self, q: Option<&CompilerQueryRequest>) -> Result { let snap = self.snapshot()?; - let analysis = self.analysis.clone(); - let rev_lock = analysis.lock_revision(q); - - Ok(LspQuerySnapshot { - snap, - analysis, - rev_lock, - }) + Ok(self.analysis.clone().query_snapshot(snap, q)) } pub fn do_interrupt(compiler: &mut LspProjectCompiler, intr: Interrupt) { @@ -451,12 +443,15 @@ impl CompileHandlerImpl { let snap = snap.clone(); let editor_tx = self.editor_tx.clone(); - let enc = self.analysis.position_encoding; + let analysis = self.analysis.clone(); rayon::spawn(move || { - let world = snap.world(); + let world = snap.world().clone(); + let mut ctx = analysis.enter(world); // todo: check all errors in this file - let diagnostics = tinymist_query::check_doc(world, snap.diagnostics(), enc); + let Some(diagnostics) = CheckRequest { snap }.request(&mut ctx) else { + return; + }; log::trace!("notify diagnostics({dv:?}): {diagnostics:#?}"); @@ -640,53 +635,3 @@ impl CompileHandler for CompileHandlerImpl } pub type QuerySnapWithStat = (LspQuerySnapshot, QueryStatGuard); - -pub struct LspQuerySnapshot { - pub snap: LspComputeGraph, - analysis: Arc, - rev_lock: AnalysisRevLock, -} - -impl std::ops::Deref for LspQuerySnapshot { - type Target = LspComputeGraph; - - fn deref(&self) -> &Self::Target { - &self.snap - } -} - -impl LspQuerySnapshot { - pub fn task(mut self, inputs: TaskInputs) -> Self { - self.snap = self.snap.task(inputs); - self - } - - pub fn run_stateful( - self, - query: T, - wrapper: fn(Option) -> CompilerQueryResponse, - ) -> Result { - let graph = self.snap.clone(); - self.run_analysis(|ctx| query.request(ctx, graph)) - .map(wrapper) - } - - pub fn run_semantic( - self, - query: T, - wrapper: fn(Option) -> CompilerQueryResponse, - ) -> Result { - self.run_analysis(|ctx| query.request(ctx)).map(wrapper) - } - - pub fn run_analysis(self, f: impl FnOnce(&mut LocalContextGuard) -> T) -> Result { - let world = self.snap.world().clone(); - let Some(..) = world.main_id() else { - log::error!("Project: main file is not set"); - bail!("main file is not set"); - }; - - let mut analysis = self.analysis.snapshot_(world, self.rev_lock); - Ok(f(&mut analysis)) - } -} diff --git a/crates/tinymist/src/task/user_action.rs b/crates/tinymist/src/task/user_action.rs index 82d2c99b..f2650a75 100644 --- a/crates/tinymist/src/task/user_action.rs +++ b/crates/tinymist/src/task/user_action.rs @@ -177,7 +177,8 @@ async fn trace_main( let timings = writer.into_inner().unwrap(); let handle = &state.project; - let diagnostics = tinymist_query::check_doc(w, diags.iter(), handle.analysis.position_encoding); + let diagnostics = + tinymist_query::convert_diagnostics(w, diags.iter(), handle.analysis.position_encoding); let rpc_kind = rpc_kind.as_str(); diff --git a/crates/tinymist/src/tool/testing.rs b/crates/tinymist/src/tool/testing.rs index c6fbdd22..538e7c16 100644 --- a/crates/tinymist/src/tool/testing.rs +++ b/crates/tinymist/src/tool/testing.rs @@ -240,7 +240,7 @@ pub async fn test_main(args: TestArgs) -> Result<()> { } fn test_once(world: &LspWorld, ctx: &TestContext) -> Result { - let mut actx = ctx.analysis.snapshot(world.clone()); + let mut actx = ctx.analysis.enter(world.clone()); let doc = typst::compile::(&actx.world).output?; let suites =