refactor: prepare for linting on syntaxes (#1640)

* refactor: for query

* refactor: for diag

* feat: lazy hash expr info

* feat: hash resolves

* fix: update snapshot
This commit is contained in:
Myriad-Dreamin 2025-04-09 04:10:47 +08:00 committed by GitHub
parent 01afa463f4
commit 054d3aecc0
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
18 changed files with 261 additions and 241 deletions

View file

@ -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<Func>;
@ -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<Analysis>,
/// 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<T: StatefulRequest>(
self,
query: T,
wrapper: fn(Option<T::Response>) -> CompilerQueryResponse,
) -> Result<CompilerQueryResponse> {
let graph = self.snap.clone();
self.run_analysis(|ctx| query.request(ctx, graph))
.map(wrapper)
}
/// Runs a semantic query.
pub fn run_semantic<T: SemanticRequest>(
self,
query: T,
wrapper: fn(Option<T::Response>) -> CompilerQueryResponse,
) -> Result<CompilerQueryResponse> {
self.run_analysis(|ctx| query.request(ctx)).map(wrapper)
}
/// Runs a query.
pub fn run_analysis<T>(self, f: impl FnOnce(&mut LocalContextGuard) -> T) -> Result<T> {
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))

View file

@ -354,7 +354,7 @@ fn value_to_def(value: Value, name: impl FnOnce() -> Option<Interned<str>>) -> O
}
struct DefResolver {
ei: Arc<ExprInfo>,
ei: ExprInfo,
}
impl DefResolver {

View file

@ -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<Interned<str>> = 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<Self>,
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<Arc<ExprInfo>> {
pub(crate) fn expr_stage_by_id(&mut self, fid: TypstFileId) -> Option<ExprInfo> {
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<ExprInfo> {
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<Self>, fid: TypstFileId) -> Option<Arc<ExprInfo>> {
pub(crate) fn expr_stage_by_id(self: &Arc<Self>, fid: TypstFileId) -> Option<ExprInfo> {
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<Self>, source: &Source) -> Arc<ExprInfo> {
pub(crate) fn expr_stage(self: &Arc<Self>, source: &Source) -> ExprInfo {
let mut route = ExprRoute::default();
self.expr_stage_(source, &mut route)
}
@ -707,7 +720,7 @@ impl SharedContext {
self: &Arc<Self>,
source: &Source,
route: &mut ExprRoute,
) -> Arc<ExprInfo> {
) -> 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<Arc<ExprInfo>>,
expr_stage: OnceLock<ExprInfo>,
type_check: OnceLock<Arc<TypeInfo>>,
}
@ -1243,7 +1256,7 @@ impl Drop for AnalysisRevLock {
#[derive(Default, Clone)]
struct AnalysisRevSlot {
revision: usize,
expr_stage: IncrCacheMap<u128, Arc<ExprInfo>>,
expr_stage: IncrCacheMap<u128, ExprInfo>,
type_check: IncrCacheMap<u128, Arc<TypeInfo>>,
}

View file

@ -309,7 +309,7 @@ pub(crate) struct Tokenizer {
pos_offset: usize,
output: Vec<SemanticToken>,
source: Source,
ei: Arc<ExprInfo>,
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<ExprInfo>,
ei: ExprInfo,
allow_multiline_token: bool,
encoding: PositionEncoding,
) -> Self {

View file

@ -25,13 +25,13 @@ pub(crate) use select::*;
#[derive(Default)]
pub struct TypeEnv {
visiting: FxHashMap<TypstFileId, Arc<TypeInfo>>,
exprs: FxHashMap<TypstFileId, Option<Arc<ExprInfo>>>,
exprs: FxHashMap<TypstFileId, Option<ExprInfo>>,
}
/// Type checking at the source unit level.
pub(crate) fn type_check(
ctx: Arc<SharedContext>,
ei: Arc<ExprInfo>,
ei: ExprInfo,
env: &mut TypeEnv,
) -> Arc<TypeInfo> {
let mut info = TypeInfo::default();
@ -82,7 +82,7 @@ type CallCacheDesc = (
pub(crate) struct TypeChecker<'a> {
ctx: Arc<SharedContext>,
ei: Arc<ExprInfo>,
ei: ExprInfo,
info: TypeInfo,
module_exports: FxHashMap<(TypstFileId, Interned<str>), OnceLock<Option<Ty>>>,

View file

@ -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<Self::Response> {
let worker = DiagWorker::new(ctx);
Some(worker.check().convert_all(self.snap.diagnostics()))
}
}

View file

@ -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<Item = &'a TypstDiagnostic>,
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,12 +90,11 @@ impl<'w> CheckDocWorker<'w> {
}
}
}
}
fn convert_diagnostic(
ctx: &CheckDocWorker,
fn convert_diagnostic(
&self,
typst_diagnostic: &TypstDiagnostic,
) -> anyhow::Result<(Url, Diagnostic)> {
) -> anyhow::Result<(Url, Diagnostic)> {
let typst_diagnostic = {
let mut diag = Cow::Borrowed(typst_diagnostic);
@ -120,87 +111,61 @@ fn convert_diagnostic(
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 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: Some(tracepoints),
related_information: (!typst_diagnostic.trace.is_empty()).then(|| {
typst_diagnostic
.trace
.iter()
.flat_map(|tracepoint| self.to_related_info(tracepoint))
.collect()
}),
..Default::default()
};
Ok((uri, diagnostic))
}
}
fn tracepoint_to_relatedinformation(
ctx: &CheckDocWorker,
fn to_related_info(
&self,
tracepoint: &Spanned<Tracepoint>,
position_encoding: PositionEncoding,
) -> anyhow::Result<Option<DiagnosticRelatedInformation>> {
if let Some(id) = tracepoint.span.id() {
let uri = ctx.uri_for_id(id)?;
let source = ctx.source(id)?;
) -> Option<DiagnosticRelatedInformation> {
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()?;
if let Some(typst_range) = source.range(tracepoint.span) {
let lsp_range = to_lsp_range(typst_range, &source, position_encoding);
let typst_range = source.range(tracepoint.span)?;
let lsp_range = self.ctx.to_lsp_range(typst_range, &source);
return Ok(Some(DiagnosticRelatedInformation {
Some(DiagnosticRelatedInformation {
location: LspLocation {
uri,
range: lsp_range,
},
message: tracepoint.v.to_string(),
}));
}
})
}
Ok(None)
}
fn diagnostic_related_information(
project: &CheckDocWorker,
typst_diagnostic: &TypstDiagnostic,
position_encoding: PositionEncoding,
) -> anyhow::Result<Vec<DiagnosticRelatedInformation>> {
let mut tracepoints = vec![];
for tracepoint in &typst_diagnostic.trace {
if let Some(info) =
tracepoint_to_relatedinformation(project, tracepoint, position_encoding)?
{
tracepoints.push(info);
}
}
Ok(tracepoints)
}
fn diagnostic_span_id(
ctx: &CheckDocWorker,
typst_diagnostic: &TypstDiagnostic,
) -> (TypstFileId, Span) {
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(|| (ctx.main(), Span::detached()))
}
.unwrap_or_else(|| (self.ctx.world.main(), Span::detached()))
}
fn diagnostic_range(
source: &Source,
typst_span: Span,
position_encoding: PositionEncoding,
) -> LspRange {
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.
@ -208,12 +173,10 @@ fn diagnostic_range(
// 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)
}
Some(node) => self.ctx.to_lsp_range(node.range(), source),
None => LspRange::new(LspPosition::new(0, 0), LspPosition::new(0, 0)),
}
}
}
fn diagnostic_severity(typst_severity: TypstSeverity) -> DiagnosticSeverity {

View file

@ -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<ExprInfo>) -> 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()?)))

View file

@ -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"
}

View file

@ -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"
}

View file

@ -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"
}

View file

@ -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"
}

View file

@ -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;

View file

@ -32,8 +32,8 @@ pub(crate) fn expr_of(
source: Source,
route: &mut ExprRoute,
guard: QueryStatGuard,
prev: Option<Arc<ExprInfo>>,
) -> Arc<ExprInfo> {
prev: Option<ExprInfo>,
) -> 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<Span, Expr, rustc_hash::FxBuildHasher>,
>,
> = Arc::new(Mutex::new(FxHashMap::default()));
let exprs: Arc<
parking_lot::lock_api::Mutex<
parking_lot::RawMutex,
HashMap<Span, Expr, rustc_hash::FxBuildHasher>,
>,
> = 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<LazyHash<ExprInfoRepr>>);
impl Deref for ExprInfo {
type Target = Arc<LazyHash<ExprInfoRepr>>;
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<H: std::hash::Hasher>(&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::<Vec<_>>();
resolves.sort_by_key(|(fid, _)| fid.into_raw());
resolves.hash(state);
let mut imports = self.imports.iter().collect::<Vec<_>>();
imports.sort_by_key(|(fid, _)| *fid);
imports.hash(state);
}
}
impl ExprInfo {
impl ExprInfoRepr {
pub fn get_def(&self, decl: &Interned<Decl>) -> Option<Expr> {
if decl.is_def() {
return Some(Expr::Decl(decl.clone()));

View file

@ -90,7 +90,7 @@ pub fn run_with_ctx<T>(
},
..Analysis::default()
})
.snapshot(world);
.enter(world);
ctx.test_package_list(|| {
vec![(

View file

@ -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<LspQuerySnapshot> {
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<LspCompilerFeat>) {
@ -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<LspCompilerFeat, ProjectInsStateExt> for CompileHandlerImpl
}
pub type QuerySnapWithStat = (LspQuerySnapshot, QueryStatGuard);
pub struct LspQuerySnapshot {
pub snap: LspComputeGraph,
analysis: Arc<Analysis>,
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<T: StatefulRequest>(
self,
query: T,
wrapper: fn(Option<T::Response>) -> CompilerQueryResponse,
) -> Result<CompilerQueryResponse> {
let graph = self.snap.clone();
self.run_analysis(|ctx| query.request(ctx, graph))
.map(wrapper)
}
pub fn run_semantic<T: SemanticRequest>(
self,
query: T,
wrapper: fn(Option<T::Response>) -> CompilerQueryResponse,
) -> Result<CompilerQueryResponse> {
self.run_analysis(|ctx| query.request(ctx)).map(wrapper)
}
pub fn run_analysis<T>(self, f: impl FnOnce(&mut LocalContextGuard) -> T) -> Result<T> {
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))
}
}

View file

@ -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();

View file

@ -240,7 +240,7 @@ pub async fn test_main(args: TestArgs) -> Result<()> {
}
fn test_once(world: &LspWorld, ctx: &TestContext) -> Result<bool> {
let mut actx = ctx.analysis.snapshot(world.clone());
let mut actx = ctx.analysis.enter(world.clone());
let doc = typst::compile::<TypstPagedDocument>(&actx.world).output?;
let suites =