1use std::hash::Hash;
2use std::num::NonZeroUsize;
3use std::ops::DerefMut;
4use std::sync::OnceLock;
5use std::sync::atomic::{AtomicU64, Ordering};
6use std::{collections::HashSet, ops::Deref};
7
8use comemo::{Track, Tracked};
9use ecow::EcoString;
10use lsp_types::Url;
11use parking_lot::Mutex;
12use rayon::iter::{IntoParallelRefIterator, ParallelIterator};
13use rustc_hash::FxHashMap;
14use tinymist_analysis::docs::DocString;
15use tinymist_analysis::stats::{AllocStats, QueryStatReportEntry};
16use tinymist_analysis::syntax::classify_def_loosely;
17use tinymist_analysis::ty::{BuiltinTy, InsTy, term_value};
18use tinymist_analysis::{analyze_expr_, analyze_import_};
19use tinymist_lint::{KnownIssues, LintInfo};
20use tinymist_project::{LspComputeGraph, LspWorld, TaskWhen};
21use tinymist_std::hash::{FxDashMap, hash128};
22use tinymist_std::typst::TypstDocument;
23use tinymist_world::debug_loc::DataSource;
24use tinymist_world::package::registry::PackageIndexEntry;
25use tinymist_world::vfs::{PathResolution, WorkspaceResolver};
26use tinymist_world::{DETACHED_ENTRY, EntryReader};
27use typst::diag::{At, FileError, FileResult, SourceDiagnostic, SourceResult, StrResult};
28use typst::foundations::{Bytes, IntoValue, Module, NativeElement, StyleChain, Styles};
29use typst::introspection::Introspector;
30use typst::introspection::PagedPosition as Position;
31use typst::model::BibliographyElem;
32use typst::syntax::package::PackageManifest;
33use typst::syntax::{Span, VirtualPath};
34use typst_shim::eval::{Eval, eval_compat};
35use typst_shim::syntax::VirtualPathExt;
36
37use super::{
38 AnalysisComponent, ComponentCoordinator, DependencyAdmission, ExprStage, LspQuerySnapshot,
39 type_check_component,
40};
41use crate::adt::revision::{RevisionLock, RevisionManager, RevisionManagerLike, RevisionSlot};
42use crate::analysis::prelude::*;
43use crate::analysis::{
44 AnalysisStats, BibInfo, CompletionFeat, Definition, PathKind, QueryStatGuard,
45 SemanticTokenCache, SemanticTokenContext, SemanticTokens, Signature, SignatureTarget, Ty,
46 TypeInfo, analyze_signature, bib_info, definition, post_type_check,
47};
48use crate::docs::{DefDocs, TidyModuleDocs};
49use crate::syntax::{
50 Decl, DefKind, ExprInfo, LexicalScope, ModuleDependency, SyntaxClass, classify_syntax,
51 construct_module_dependencies, is_mark, resolve_id_by_path, scan_workspace_files,
52};
53use crate::upstream::{Tooltip, tooltip_};
54use crate::{
55 ColorTheme, CompilerQueryRequest, LspPosition, LspRange, LspWorldExt, PositionEncoding,
56};
57
58macro_rules! interned_str {
59 ($name:ident, $value:expr) => {
60 static $name: LazyLock<Interned<str>> = LazyLock::new(|| $value.into());
61 };
62}
63
64#[derive(Default, Clone)]
66pub struct Analysis {
67 pub position_encoding: PositionEncoding,
69 pub allow_overlapping_token: bool,
71 pub allow_multiline_token: bool,
73 pub remove_html: bool,
75 pub support_client_codelens: bool,
77 pub extended_code_action: bool,
85 pub completion_feat: CompletionFeat,
87 pub color_theme: ColorTheme,
89 pub lint: TaskWhen,
91 pub periscope: Option<Arc<dyn PeriscopeProvider + Send + Sync>>,
93 pub workers: Arc<AnalysisGlobalWorkers>,
95 pub local_packages: Arc<Mutex<OnceLock<EcoVec<PackageIndexEntry>>>>,
97 pub tokens_caches: Arc<Mutex<SemanticTokenCache>>,
99 pub caches: AnalysisGlobalCaches,
101 pub analysis_rev_cache: Arc<Mutex<AnalysisRevCache>>,
103 pub stats: Arc<AnalysisStats>,
105}
106
107impl Analysis {
108 pub fn enter(&self, g: LspComputeGraph) -> LocalContextGuard {
110 self.enter_(g, self.lock_revision(None))
111 }
112
113 pub(crate) fn enter_(&self, g: LspComputeGraph, mut lg: AnalysisRevLock) -> LocalContextGuard {
115 let lifetime = self.caches.lifetime.fetch_add(1, Ordering::SeqCst);
116 let slot = self
117 .analysis_rev_cache
118 .lock()
119 .find_revision(g.world().revision(), &lg);
120 let tokens = lg.tokens.take();
121 LocalContextGuard {
122 _rev_lock: lg,
123 local: LocalContext {
124 tokens,
125 caches: AnalysisLocalCaches::default(),
126 shared: Arc::new(SharedContext {
127 slot,
128 lifetime,
129 graph: g,
130 analysis: self.clone(),
131 }),
132 },
133 }
134 }
135
136 pub fn query_snapshot(
138 self: Arc<Self>,
139 snap: LspComputeGraph,
140 req: Option<&CompilerQueryRequest>,
141 ) -> LspQuerySnapshot {
142 let rev_lock = self.lock_revision(req);
143 LspQuerySnapshot {
144 snap,
145 analysis: self,
146 rev_lock,
147 }
148 }
149
150 #[must_use]
152 pub fn lock_revision(&self, req: Option<&CompilerQueryRequest>) -> AnalysisRevLock {
153 let mut grid = self.analysis_rev_cache.lock();
154
155 AnalysisRevLock {
156 tokens: match req {
157 Some(CompilerQueryRequest::SemanticTokensFull(req)) => Some(
158 SemanticTokenCache::acquire(self.tokens_caches.clone(), &req.path, None),
159 ),
160 Some(CompilerQueryRequest::SemanticTokensDelta(req)) => {
161 Some(SemanticTokenCache::acquire(
162 self.tokens_caches.clone(),
163 &req.path,
164 Some(&req.previous_result_id),
165 ))
166 }
167 _ => None,
168 },
169 inner: grid.manager.lock_estimated(),
170 grid: self.analysis_rev_cache.clone(),
171 }
172 }
173
174 pub fn clear_cache(&self) {
176 self.caches.signatures.clear();
177 self.caches.docstrings.clear();
178 self.caches.def_signatures.clear();
179 self.caches.static_signatures.clear();
180 self.caches.terms.clear();
181 *self.local_packages.lock() = OnceLock::default();
182 self.tokens_caches.lock().clear();
183 self.analysis_rev_cache.lock().clear();
184 }
185
186 pub fn report_query_stats(&self) -> String {
188 self.stats.report()
189 }
190
191 pub fn report_query_stats_json(&self) -> Vec<QueryStatReportEntry> {
193 self.stats.report_json()
194 }
195
196 pub fn report_alloc_stats(&self) -> String {
198 AllocStats::report()
199 }
200
201 pub fn trigger_suggest(&self, context: bool) -> Option<Interned<str>> {
203 interned_str!(INTERNED, "editor.action.triggerSuggest");
204
205 (self.completion_feat.trigger_suggest && context).then(|| INTERNED.clone())
206 }
207
208 pub fn trigger_parameter_hints(&self, context: bool) -> Option<Interned<str>> {
210 interned_str!(INTERNED, "editor.action.triggerParameterHints");
211 (self.completion_feat.trigger_parameter_hints && context).then(|| INTERNED.clone())
212 }
213
214 pub fn trigger_on_snippet(&self, context: bool) -> Option<Interned<str>> {
221 if !self.completion_feat.trigger_on_snippet_placeholders {
222 return None;
223 }
224
225 self.trigger_suggest(context)
226 }
227
228 pub fn trigger_on_snippet_with_param_hint(&self, context: bool) -> Option<Interned<str>> {
230 interned_str!(INTERNED, "tinymist.triggerSuggestAndParameterHints");
231 if !self.completion_feat.trigger_on_snippet_placeholders {
232 return self.trigger_parameter_hints(context);
233 }
234
235 (self.completion_feat.trigger_suggest_and_parameter_hints && context)
236 .then(|| INTERNED.clone())
237 }
238}
239
240pub trait PeriscopeProvider {
242 fn periscope_at(
244 &self,
245 _ctx: &mut LocalContext,
246 _doc: &TypstDocument,
247 _pos: Position,
248 ) -> Option<String> {
249 None
250 }
251}
252
253pub struct LocalContextGuard {
255 pub local: LocalContext,
257 _rev_lock: AnalysisRevLock,
259}
260
261impl Deref for LocalContextGuard {
262 type Target = LocalContext;
263
264 fn deref(&self) -> &Self::Target {
265 &self.local
266 }
267}
268
269impl DerefMut for LocalContextGuard {
270 fn deref_mut(&mut self) -> &mut Self::Target {
271 &mut self.local
272 }
273}
274
275impl Drop for LocalContextGuard {
277 fn drop(&mut self) {
278 self.gc();
279 }
280}
281
282impl LocalContextGuard {
283 fn gc(&self) {
284 let lifetime = self.lifetime;
285 loop {
286 let latest_clear_lifetime = self.analysis.caches.clear_lifetime.load(Ordering::Relaxed);
287 if latest_clear_lifetime >= lifetime {
288 return;
289 }
290
291 if self.analysis.caches.clear_lifetime.compare_exchange(
292 latest_clear_lifetime,
293 lifetime,
294 Ordering::SeqCst,
295 Ordering::SeqCst,
296 ) != Ok(latest_clear_lifetime)
297 {
298 continue;
299 }
300
301 break;
302 }
303
304 let retainer = |l: u64| lifetime.saturating_sub(l) < 60;
305 let caches = &self.analysis.caches;
306 caches.def_signatures.retain(|(l, _)| retainer(*l));
307 caches.static_signatures.retain(|(l, _)| retainer(*l));
308 caches.terms.retain(|(l, _)| retainer(*l));
309 caches.signatures.retain(|(l, _)| retainer(*l));
310 caches.docstrings.retain(|(l, _)| retainer(*l));
311 }
312}
313
314pub struct LocalContext {
317 pub(crate) tokens: Option<SemanticTokenContext>,
319 pub caches: AnalysisLocalCaches,
321 pub shared: Arc<SharedContext>,
323}
324
325impl Deref for LocalContext {
326 type Target = Arc<SharedContext>;
327
328 fn deref(&self) -> &Self::Target {
329 &self.shared
330 }
331}
332
333impl DerefMut for LocalContext {
334 fn deref_mut(&mut self) -> &mut Self::Target {
335 &mut self.shared
336 }
337}
338
339impl LocalContext {
340 #[cfg(test)]
342 pub fn test_package_list(&mut self, f: impl FnOnce() -> Vec<PackageIndexEntry> + Clone) {
343 self.world().registry.test_package_list(f.clone());
344 self.analysis
345 .local_packages
346 .lock()
347 .get_or_init(|| f().into_iter().collect());
348 }
349
350 #[cfg(test)]
352 pub fn test_completion_files(&mut self, f: impl FnOnce() -> Vec<TypstFileId>) {
353 self.caches.completion_files.get_or_init(f);
354 }
355
356 #[cfg(test)]
358 pub fn test_files(&mut self, f: impl FnOnce() -> Vec<TypstFileId>) {
359 self.caches.root_files.get_or_init(f);
360 }
361
362 pub(crate) fn completion_files(&self, pref: &PathKind) -> impl Iterator<Item = &TypstFileId> {
364 let regexes = pref.ext_matcher();
365 self.caches
366 .completion_files
367 .get_or_init(|| {
368 if let Some(root) = self.world().entry_state().workspace_root() {
369 scan_workspace_files(&root, PathKind::Special.ext_matcher(), |path| {
370 VirtualPath::virtualize(&root, &root.join(path))
371 .ok()
372 .map(|path| WorkspaceResolver::workspace_file(Some(&root), path))
373 })
374 .into_iter()
375 .flatten()
376 .collect()
377 } else {
378 vec![]
379 }
380 })
381 .iter()
382 .filter(move |fid| {
383 fid.vpath()
384 .as_rooted_path_compat()
385 .extension()
386 .and_then(|path| path.to_str())
387 .is_some_and(|path| regexes.is_match(path))
388 })
389 }
390
391 pub fn source_files(&self) -> &Vec<TypstFileId> {
393 self.caches.root_files.get_or_init(|| {
394 self.completion_files(&PathKind::Source {
395 allow_package: false,
396 })
397 .copied()
398 .collect()
399 })
400 }
401
402 pub fn module_dependencies(&mut self) -> &HashMap<TypstFileId, ModuleDependency> {
404 if self.caches.module_deps.get().is_some() {
405 self.caches.module_deps.get().unwrap()
406 } else {
407 let deps = construct_module_dependencies(self);
410 self.caches.module_deps.get_or_init(|| deps)
411 }
412 }
413
414 pub fn depended_source_files(&self) -> EcoVec<TypstFileId> {
416 let mut ids = self.depended_files();
417 let preference = PathKind::Source {
418 allow_package: false,
419 };
420 ids.retain(|id| preference.is_match(id.vpath().as_rooted_path_compat()));
421 ids
422 }
423
424 pub fn depended_files(&self) -> EcoVec<TypstFileId> {
427 self.world().depended_files()
428 }
429
430 pub fn shared(&self) -> &Arc<SharedContext> {
432 &self.shared
433 }
434
435 pub fn shared_(&self) -> Arc<SharedContext> {
437 self.shared.clone()
438 }
439
440 pub fn fork_for_search(&mut self) -> SearchCtx<'_> {
442 SearchCtx {
443 ctx: self,
444 searched: Default::default(),
445 worklist: Default::default(),
446 }
447 }
448
449 pub(crate) fn preload_package(&self, entry_point: TypstFileId) {
450 self.shared_().preload_package(entry_point);
451 }
452
453 pub(crate) fn preload_expr_stages<I>(&self, files: I)
454 where
455 I: IntoIterator<Item = TypstFileId>,
456 {
457 self.shared_().preload_expr_stages(files);
458 }
459
460 pub(crate) fn with_vm<T>(&self, f: impl FnOnce(&mut typst_shim::eval::Vm) -> T) -> T {
461 crate::upstream::with_vm((self.world() as &dyn World).track(), f)
462 }
463
464 pub(crate) fn const_eval(&self, rr: ast::Expr<'_>) -> Option<Value> {
465 SharedContext::const_eval(rr)
466 }
467
468 pub(crate) fn mini_eval(&self, rr: ast::Expr<'_>) -> Option<Value> {
469 self.const_eval(rr)
470 .or_else(|| self.with_vm(|vm| rr.eval(vm).ok()))
471 }
472
473 pub(crate) fn cached_tokens(&mut self, source: &Source) -> (SemanticTokens, Option<String>) {
474 let tokens = crate::analysis::semantic_tokens::get_semantic_tokens(self.shared(), source);
475
476 let result_id = self.tokens.as_ref().map(|t| {
477 let id = t.next.revision;
478 t.next
479 .data
480 .set(tokens.clone())
481 .unwrap_or_else(|_| panic!("unexpected slot overwrite {id}"));
482 id.to_string()
483 });
484 (tokens, result_id)
485 }
486
487 pub(crate) fn expr_stage_by_id(&mut self, fid: TypstFileId) -> Option<ExprInfo> {
489 Some(self.expr_stage(&self.source_by_id(fid).ok()?))
490 }
491
492 pub(crate) fn expr_stage(&mut self, source: &Source) -> ExprInfo {
494 let id = source.id();
495 let cache = &self.caches.modules.entry(id).or_default().expr_stage;
496 cache.get_or_init(|| self.shared.expr_stage(source)).clone()
497 }
498
499 pub(crate) fn type_check(&mut self, source: &Source) -> Arc<TypeInfo> {
501 let id = source.id();
502 let cache = &self.caches.modules.entry(id).or_default().type_check;
503 cache.get_or_init(|| self.shared.type_check(source)).clone()
504 }
505
506 pub(crate) fn lint(
507 &mut self,
508 source: &Source,
509 known_issues: &KnownIssues,
510 ) -> EcoVec<SourceDiagnostic> {
511 self.shared.lint(source, known_issues).diagnostics
512 }
513
514 pub(crate) fn type_check_by_id(&mut self, id: TypstFileId) -> Arc<TypeInfo> {
516 let cache = &self.caches.modules.entry(id).or_default().type_check;
517 cache
518 .clone()
519 .get_or_init(|| {
520 let source = self.source_by_id(id).ok();
521 source
522 .map(|s| self.shared.type_check(&s))
523 .unwrap_or_default()
524 })
525 .clone()
526 }
527
528 pub(crate) fn type_of_span(&mut self, s: Span) -> Option<Ty> {
529 let scheme = self.type_check_by_id(s.id()?);
530 let ty = scheme.type_of_span(s)?;
531 Some(scheme.simplify(ty, false))
532 }
533
534 pub(crate) fn def_docs(&mut self, def: &Definition) -> Option<DefDocs> {
535 match def.decl.kind() {
538 DefKind::Function => {
539 let sig = self.sig_of_def(def.clone())?;
540 let docs = crate::docs::sig_docs(self.shared(), &sig)?;
541 Some(DefDocs::Function(Box::new(docs)))
542 }
543 DefKind::Struct | DefKind::Constant | DefKind::Variable => {
544 let docs = crate::docs::var_docs(self.shared(), def.decl.span())?;
545 Some(DefDocs::Variable(docs))
546 }
547 DefKind::Module => {
548 let ei = self.expr_stage_by_id(def.decl.file_id()?)?;
549 Some(DefDocs::Module(TidyModuleDocs {
550 docs: ei.module_docstring.docs.clone().unwrap_or_default(),
551 }))
552 }
553 DefKind::Reference => None,
554 }
555 }
556}
557
558#[derive(Clone)]
560pub struct SharedQueryCache<K, V> {
561 slots: Arc<FxDashMap<K, Arc<OnceLock<V>>>>,
562}
563
564impl<K, V> Default for SharedQueryCache<K, V>
565where
566 K: Eq + Hash,
567{
568 fn default() -> Self {
569 Self {
570 slots: Arc::new(FxDashMap::default()),
571 }
572 }
573}
574
575impl<K, V> SharedQueryCache<K, V>
576where
577 K: Eq + Hash,
578 V: Clone,
579{
580 pub fn get_or_init(&self, key: K, init: impl FnOnce() -> V) -> V {
582 let slot = self
583 .slots
584 .entry(key)
585 .or_insert_with(|| Arc::new(OnceLock::new()))
586 .clone();
587 slot.get_or_init(init).clone()
588 }
589}
590
591pub struct SharedContext {
593 pub lifetime: u64,
595 pub graph: LspComputeGraph,
599 pub analysis: Analysis,
601 slot: Arc<RevisionSlot<AnalysisRevSlot>>,
603}
604
605fn component_member<T: Clone>(
607 kind: &str,
608 component: &AnalysisComponent,
609 result: &FxHashMap<TypstFileId, T>,
610 fid: TypstFileId,
611) -> T {
612 result
613 .get(&fid)
614 .unwrap_or_else(|| {
615 panic!(
616 "{kind} component {:?} is missing requested file {fid:?}",
617 component.members
618 )
619 })
620 .clone()
621}
622
623impl SharedContext {
624 pub(super) fn components(&self) -> &ComponentCoordinator {
625 &self.slot.components
626 }
627
628 pub fn revision(&self) -> usize {
630 self.slot.revision
631 }
632
633 pub(crate) fn position_encoding(&self) -> PositionEncoding {
635 self.analysis.position_encoding
636 }
637
638 pub fn world(&self) -> &LspWorld {
640 self.graph.world()
641 }
642
643 pub fn success_doc(&self) -> Option<&TypstDocument> {
645 self.graph.snap.success_doc.as_ref()
646 }
647
648 pub fn to_typst_pos(&self, position: LspPosition, src: &Source) -> Option<usize> {
650 crate::to_typst_position(position, self.analysis.position_encoding, src)
651 }
652
653 pub fn to_typst_pos_offset(
655 &self,
656 source: &Source,
657 position: LspPosition,
658 shift: usize,
659 ) -> Option<usize> {
660 let offset = self.to_typst_pos(position, source)?;
661 Some(ceil_char_boundary(source.text(), offset + shift))
662 }
663
664 pub fn to_lsp_pos(&self, typst_offset: usize, src: &Source) -> LspPosition {
666 crate::to_lsp_position(typst_offset, self.analysis.position_encoding, src)
667 }
668
669 pub fn to_typst_range(&self, position: LspRange, src: &Source) -> Option<Range<usize>> {
671 crate::to_typst_range(position, self.analysis.position_encoding, src)
672 }
673
674 pub fn to_lsp_range(&self, position: Range<usize>, src: &Source) -> LspRange {
676 crate::to_lsp_range(position, src, self.analysis.position_encoding)
677 }
678
679 pub fn to_lsp_range_(&self, position: Range<usize>, fid: TypstFileId) -> Option<LspRange> {
681 let ext = fid
682 .vpath()
683 .as_rootless_path_compat()
684 .extension()
685 .and_then(|ext| ext.to_str());
686 if matches!(ext, Some("yaml" | "yml" | "bib")) {
688 let bytes = self.file_by_id(fid).ok()?;
689 let bytes_len = bytes.len();
690 let loc = loc_info(bytes)?;
691 let start = find_loc(bytes_len, &loc, position.start, self.position_encoding())?;
693 let end = find_loc(bytes_len, &loc, position.end, self.position_encoding())?;
694 return Some(LspRange { start, end });
695 }
696
697 let source = self.source_by_id(fid).ok()?;
698
699 Some(self.to_lsp_range(position, &source))
700 }
701
702 pub fn path_for_id(&self, id: TypstFileId) -> Result<PathResolution, FileError> {
704 self.world().path_for_id(id)
705 }
706
707 pub fn uri_for_id(&self, fid: TypstFileId) -> Result<Url, FileError> {
709 self.world().uri_for_id(fid)
710 }
711
712 pub fn file_id_by_path(&self, path: &Path) -> FileResult<TypstFileId> {
714 self.world().file_id_by_path(path)
715 }
716
717 pub fn file_by_id(&self, fid: TypstFileId) -> FileResult<Bytes> {
719 self.world().file(fid)
720 }
721
722 pub fn source_by_id(&self, fid: TypstFileId) -> FileResult<Source> {
724 self.world().source(fid)
725 }
726
727 pub fn source_by_path(&self, path: &Path) -> FileResult<Source> {
729 self.source_by_id(self.file_id_by_path(path)?)
730 }
731
732 pub fn classify_span<'s>(&self, source: &'s Source, span: Span) -> Option<SyntaxClass<'s>> {
735 let node = LinkedNode::new(source.root()).find(span)?;
736 let cursor = node.offset() + 1;
737 classify_syntax(node, cursor)
738 }
739
740 pub fn classify_for_decl<'s>(
744 &self,
745 source: &'s Source,
746 position: LspPosition,
747 ) -> Option<SyntaxClass<'s>> {
748 let cursor = self.to_typst_pos_offset(source, position, 1)?;
749 let mut node = LinkedNode::new(source.root()).leaf_at_compat(cursor)?;
750
751 if cursor == node.offset() + 1 && is_mark(node.kind()) {
754 let prev_leaf = node.prev_leaf();
755 if let Some(prev_leaf) = prev_leaf
756 && prev_leaf.range().end == node.offset()
757 {
758 node = prev_leaf;
759 }
760 }
761
762 classify_syntax(node, cursor)
763 }
764
765 pub fn font_info(&self, font: typst::text::Font) -> Option<Arc<DataSource>> {
767 self.world().font_resolver.describe_font(&font)
768 }
769
770 pub fn non_preview_packages(&self) -> EcoVec<PackageIndexEntry> {
773 #[cfg(feature = "local-registry")]
774 let it = || {
775 crate::package::list_package(
776 self.world(),
777 crate::package::PackageFilter::ExceptFor(EcoString::inline("preview")),
778 )
779 };
780 #[cfg(not(feature = "local-registry"))]
781 let it = || Default::default();
782 self.analysis.local_packages.lock().get_or_init(it).clone()
783 }
784
785 pub(crate) fn const_eval(rr: ast::Expr<'_>) -> Option<Value> {
786 Some(match rr {
787 ast::Expr::None(_) => Value::None,
788 ast::Expr::Auto(_) => Value::Auto,
789 ast::Expr::Bool(v) => Value::Bool(v.get()),
790 ast::Expr::Int(v) => Value::Int(v.get()),
791 ast::Expr::Float(v) => Value::Float(v.get()),
792 ast::Expr::Numeric(v) => Value::numeric(v.get()),
793 ast::Expr::Str(v) => Value::Str(v.get().into()),
794 _ => return None,
795 })
796 }
797
798 pub fn module_by_id(&self, fid: TypstFileId) -> SourceResult<Module> {
800 let source = self.source_by_id(fid).at(Span::detached())?;
801 self.module_by_src(source)
802 }
803
804 pub fn module_by_str(&self, rr: String) -> Option<Module> {
806 let src = Source::new(*DETACHED_ENTRY, rr);
807 self.module_by_src(src).ok()
808 }
809
810 pub fn module_by_src(&self, source: Source) -> SourceResult<Module> {
812 eval_compat(&self.world(), &source)
813 }
814
815 pub fn module_by_syntax(self: &Arc<Self>, source: &SyntaxNode) -> Option<Value> {
817 self.module_term_by_syntax(source, true)
818 .and_then(|ty| ty.value())
819 }
820
821 pub fn module_term_by_syntax(self: &Arc<Self>, source: &SyntaxNode, value: bool) -> Option<Ty> {
824 let (src, scope) = self.analyze_import(source);
825 if let Some(scope) = scope {
826 return Some(match scope {
827 Value::Module(m) if m.file_id().is_some() => {
828 Ty::Builtin(BuiltinTy::Module(Decl::module(m.file_id()?).into()))
829 }
830 scope => Ty::Value(InsTy::new(scope)),
831 });
832 }
833
834 match src {
835 Some(Value::Str(s)) => {
836 let id = resolve_id_by_path(self.world(), source.span().id()?, s.as_str())?;
837
838 Some(if value {
839 Ty::Value(InsTy::new(Value::Module(self.module_by_id(id).ok()?)))
840 } else {
841 Ty::Builtin(BuiltinTy::Module(Decl::module(id).into()))
842 })
843 }
844 _ => None,
845 }
846 }
847
848 pub(crate) fn expr_stage_by_id(self: &Arc<Self>, fid: TypstFileId) -> Option<ExprInfo> {
850 Some(self.expr_stage(&self.source_by_id(fid).ok()?))
851 }
852
853 fn previous_expr_component(
854 self: &Arc<Self>,
855 component: &AnalysisComponent,
856 sources: &[Source],
857 ) -> Option<FxHashMap<TypstFileId, ExprInfo>> {
858 if sources.len() != component.members.len()
859 || sources
860 .iter()
861 .zip(component.members.iter())
862 .any(|(source, member)| source.id() != *member)
863 {
864 return None;
865 }
866 if self
870 .slot
871 .components
872 .has_unresolved_dependencies(component.members[0])
873 {
874 return None;
875 }
876
877 let deps = self.slot.components.member_dependencies(component)?;
881
882 let mut previous = FxHashMap::default();
883 for source in sources {
884 let key = Self::expr_history_key(source, component, &deps);
885 let info = self.slot.expr_stage.previous(&key)?;
886 if info.fid != source.id()
887 || info.source.lines().len_bytes() != source.lines().len_bytes()
888 || hash128(&info.source) != hash128(source)
889 {
890 return None;
891 }
892 previous.insert(source.id(), info);
893 }
894
895 for (&importer, info) in &previous {
898 for (&target, old_exports) in &info.imports {
899 match self.dependency_admission(importer, target) {
900 DependencyAdmission::SameComponent => {
901 if !previous.contains_key(&target) {
902 return None;
903 }
904 }
905 DependencyAdmission::Reachable => {
906 let current = self.expr_stage_by_id(target)?.exports.clone();
907 if old_exports.size() != current.size()
908 || hash128(old_exports) != hash128(¤t)
909 {
910 return None;
911 }
912 }
913 DependencyAdmission::Unresolved | DependencyAdmission::Rejected => {
914 return None;
915 }
916 }
917 }
918 }
919
920 Some(previous)
921 }
922
923 fn expr_history_key(
926 source: &Source,
927 component: &AnalysisComponent,
928 deps: &FxHashMap<TypstFileId, Vec<TypstFileId>>,
929 ) -> u128 {
930 hash128(&(source, component.members.as_ref(), &deps[&source.id()]))
931 }
932
933 fn type_component_fingerprint(
938 self: &Arc<Self>,
939 component: &AnalysisComponent,
940 exprs: &FxHashMap<TypstFileId, ExprInfo>,
941 ) -> u128 {
942 let inputs: Vec<_> = self
943 .slot
944 .components
945 .reachable_files(component.members[0])
946 .into_iter()
947 .map(|fid| {
948 let hash = exprs
949 .get(&fid)
950 .cloned()
951 .or_else(|| self.expr_stage_by_id(fid))
952 .map(|info| hash128(&info));
953 (fid, hash)
954 })
955 .collect();
956
957 hash128(&(component.members.as_ref(), inputs))
958 }
959
960 fn expr_component(
961 self: &Arc<Self>,
962 component: &Arc<AnalysisComponent>,
963 ) -> Arc<FxHashMap<TypstFileId, ExprInfo>> {
964 let result = component
965 .expr_stage
966 .get_or_init(|| {
967 let sources: Vec<_> = component
968 .members
969 .iter()
970 .map(|&fid| {
971 self.source_by_id(fid).unwrap_or_else(|err| {
972 panic!(
973 "sealed expression component contains unreadable {fid:?}: {err:?}"
974 )
975 })
976 })
977 .collect();
978 if let Some(previous) = self.previous_expr_component(component, &sources) {
979 return Arc::new(previous);
980 }
981
982 let mut route = ExprStage::new(self.clone(), sources.iter().cloned());
983 let mut result = FxHashMap::default();
984
985 for source in sources {
986 let info = route.analyze(self.clone(), source.clone());
987 result.insert(source.id(), info);
988 }
989
990 for (&fid, info) in &result {
991 for imported in info.imports.keys() {
992 match self.slot.components.record_dependency(fid, *imported) {
993 DependencyAdmission::SameComponent
994 | DependencyAdmission::Reachable => {}
995 DependencyAdmission::Unresolved => {
996 crate::log_debug_ct!(
997 "expression analysis kept unresolved import {fid:?} -> {imported:?} as no-wait"
998 );
999 }
1000 DependencyAdmission::Rejected => {
1001 log::warn!(
1002 "dependency admission missed import {fid:?} -> {imported:?}; keeping it as no-wait"
1003 );
1004 }
1005 }
1006 }
1007 }
1008
1009 Arc::new(result)
1010 })
1011 .clone();
1012
1013 self.slot.components.commit_current(component, |deps| {
1020 for info in result.values() {
1021 let key = Self::expr_history_key(&info.source, component, deps);
1022 self.slot.expr_stage.publish(key, info.clone());
1023 }
1024 });
1025
1026 result
1027 }
1028
1029 pub(crate) fn expr_stage(self: &Arc<Self>, source: &Source) -> ExprInfo {
1031 loop {
1032 let component = self.analysis_component(source.id());
1033 let result = component
1034 .expr_stage
1035 .get()
1036 .cloned()
1037 .unwrap_or_else(|| self.expr_component(&component));
1038 if !component.is_current() {
1039 continue;
1040 }
1041
1042 return component_member("expression", &component, &result, source.id());
1043 }
1044 }
1045
1046 pub(crate) fn external_exports_of(
1047 self: &Arc<Self>,
1048 importer: TypstFileId,
1049 source: &Source,
1050 ) -> Option<Arc<LazyHash<LexicalScope>>> {
1051 match self
1055 .slot
1056 .components
1057 .record_dependency(importer, source.id())
1058 {
1059 DependencyAdmission::Reachable => Some(self.expr_stage(source).exports.clone()),
1060 DependencyAdmission::SameComponent => {
1061 None
1066 }
1067 DependencyAdmission::Unresolved => None,
1068 DependencyAdmission::Rejected => {
1069 log::warn!(
1070 "rejected expression dependency {importer:?} -> {:?}; using empty exports",
1071 source.id()
1072 );
1073 None
1074 }
1075 }
1076 }
1077
1078 pub(crate) fn type_check(self: &Arc<Self>, source: &Source) -> Arc<TypeInfo> {
1080 loop {
1081 let component = self.analysis_component(source.id());
1082 if let Some(result) = component.type_check.get() {
1083 if !component.is_current() {
1084 continue;
1085 }
1086 return component_member("type", &component, result, source.id());
1087 }
1088
1089 let exprs = self.expr_component(&component);
1090 if !component.is_current() {
1091 continue;
1092 }
1093
1094 let fingerprint = self.type_component_fingerprint(&component, &exprs);
1098 let result = component
1099 .type_check
1100 .get_or_init(|| {
1101 let mut previous = FxHashMap::default();
1102 for &fid in component.members.iter() {
1103 let ei = exprs.get(&fid).unwrap_or_else(|| {
1104 panic!("component expression batch is missing {fid:?}")
1105 });
1106 if let Some(cache_hint) = self
1107 .slot
1108 .type_check
1109 .previous(&hash128(&(fid, fingerprint)))
1110 .filter(|prev| prev.fid == Some(fid) && prev.revision == ei.revision)
1111 {
1112 previous.insert(fid, cache_hint);
1113 }
1114 }
1115 if previous.len() != component.members.len() {
1116 previous.clear();
1120 }
1121
1122 let result = type_check_component(
1123 self.clone(),
1124 &component.members,
1125 exprs.clone(),
1126 previous,
1127 );
1128
1129 Arc::new(result)
1130 })
1131 .clone();
1132
1133 let published = self.slot.components.commit_current(&component, |_deps| {
1138 for (&fid, info) in result.iter() {
1139 self.slot
1140 .type_check
1141 .publish(hash128(&(fid, fingerprint)), info.clone());
1142 }
1143 });
1144 if published.is_none() {
1145 continue;
1146 }
1147
1148 return component_member("type", &component, &result, source.id());
1149 }
1150 }
1151
1152 #[typst_macros::time(span = source.root().span())]
1154 pub(crate) fn lint(self: &Arc<Self>, source: &Source, issues: &KnownIssues) -> LintInfo {
1155 let ei = self.expr_stage(source);
1156 let guard = self.query_stat(source.id(), "lint");
1157 self.slot.lint.compute(hash128(&(&ei, issues)), |_| {
1158 guard.miss();
1159 tinymist_lint::lint_file(self.world(), &ei, issues.clone())
1160 })
1161 }
1162
1163 pub(crate) fn type_of_func(self: &Arc<Self>, func: Func) -> Signature {
1164 crate::log_debug_ct!("convert runtime func {func:?}");
1165 analyze_signature(self, SignatureTarget::Convert(func)).unwrap()
1166 }
1167
1168 pub(crate) fn type_of_value(self: &Arc<Self>, val: &Value) -> Ty {
1169 crate::log_debug_ct!("convert runtime value {val:?}");
1170
1171 let cache_key = val;
1173 let cached = self
1174 .analysis
1175 .caches
1176 .terms
1177 .m
1178 .get(&hash128(&cache_key))
1179 .and_then(|slot| (cache_key == &slot.1.0).then_some(slot.1.1.clone()));
1180 if let Some(cached) = cached {
1181 return cached;
1182 }
1183
1184 let res = term_value(val);
1185
1186 self.analysis
1187 .caches
1188 .terms
1189 .m
1190 .entry(hash128(&cache_key))
1191 .or_insert_with(|| (self.lifetime, (cache_key.clone(), res.clone())));
1192
1193 res
1194 }
1195
1196 pub(crate) fn def_of_span(self: &Arc<Self>, source: &Source, span: Span) -> Option<Definition> {
1198 let syntax = self.classify_span(source, span)?;
1199 definition(self, source, syntax)
1200 }
1201
1202 pub(crate) fn def_of_syntax(
1207 self: &Arc<Self>,
1208 source: &Source,
1209 syntax: SyntaxClass,
1210 ) -> Option<Definition> {
1211 definition(self, source, syntax)
1212 }
1213
1214 pub(crate) fn def_of_syntax_or_dyn(
1222 self: &Arc<Self>,
1223 source: &Source,
1224 syntax: SyntaxClass,
1225 ) -> Option<Definition> {
1226 let def = self.def_of_syntax(source, syntax.clone());
1227 match def.as_ref().map(|d| d.decl.kind()) {
1228 Some(DefKind::Reference | DefKind::Module | DefKind::Function) => return def,
1230 Some(DefKind::Struct | DefKind::Constant | DefKind::Variable) | None => {}
1231 }
1232
1233 let know_ty_well = def
1235 .as_ref()
1236 .and_then(|d| self.simplified_type_of_span(d.decl.span()))
1237 .filter(|ty| !matches!(ty, Ty::Any))
1238 .is_some();
1239 if know_ty_well {
1240 return def;
1241 }
1242
1243 let def_ref = def.as_ref();
1244 let def_name = || Some(def_ref?.name().clone());
1245 let dyn_def = self
1246 .analyze_expr(syntax.node())
1247 .iter()
1248 .find_map(|(value, _)| {
1249 let def = Definition::from_value(value.clone(), def_name)?;
1250 None.or_else(|| {
1251 let source = self.source_by_id(def.decl.file_id()?).ok()?;
1252 let node = LinkedNode::new(source.root()).find(def.decl.span())?;
1253 let def_at_the_span = classify_def_loosely(node)?;
1254 self.def_of_span(&source, def_at_the_span.name()?.span())
1255 })
1256 .or(Some(def))
1257 });
1258
1259 dyn_def.or(def)
1261 }
1262
1263 pub(crate) fn simplified_type_of_span(self: &Arc<Self>, span: Span) -> Option<Ty> {
1264 let source = self.source_by_id(span.id()?).ok()?;
1265 let (ti, ty) = self.type_of_span_(&source, span)?;
1266 Some(ti.simplify(ty, false))
1267 }
1268
1269 pub(crate) fn type_of_span(self: &Arc<Self>, span: Span) -> Option<Ty> {
1270 let source = self.source_by_id(span.id()?).ok()?;
1271 Some(self.type_of_span_(&source, span)?.1)
1272 }
1273
1274 pub(crate) fn type_of_span_(
1275 self: &Arc<Self>,
1276 source: &Source,
1277 span: Span,
1278 ) -> Option<(Arc<TypeInfo>, Ty)> {
1279 let ti = self.type_check(source);
1280 let ty = ti.type_of_span(span)?;
1281 Some((ti, ty))
1282 }
1283
1284 pub(crate) fn post_type_of_node(self: &Arc<Self>, node: LinkedNode) -> Option<Ty> {
1285 let id = node.span().id()?;
1286 let source = self.source_by_id(id).ok()?;
1287 let ty_chk = self.type_check(&source);
1288
1289 let ty = post_type_check(self.clone(), &ty_chk, node.clone())
1290 .or_else(|| ty_chk.type_of_span(node.span()))?;
1291 Some(ty_chk.simplify(ty, false))
1292 }
1293
1294 pub(crate) fn sig_of_def(self: &Arc<Self>, def: Definition) -> Option<Signature> {
1295 crate::log_debug_ct!("check definition func {def:?}");
1296 let source = def.decl.file_id().and_then(|id| self.source_by_id(id).ok());
1297 analyze_signature(self, SignatureTarget::Def(source, def))
1298 }
1299
1300 pub(crate) fn def_docs(self: &Arc<Self>, def: &Definition) -> Option<DefDocs> {
1301 match def.decl.kind() {
1302 DefKind::Function => {
1303 let sig = self.sig_of_def(def.clone())?;
1304 let docs = crate::docs::sig_docs(self, &sig)?;
1305 Some(DefDocs::Function(Box::new(docs)))
1306 }
1307 DefKind::Struct | DefKind::Constant | DefKind::Variable => {
1308 let docs = crate::docs::var_docs(self, def.decl.span())?;
1309 Some(DefDocs::Variable(docs))
1310 }
1311 DefKind::Module => {
1312 let ei = self.expr_stage_by_id(def.decl.file_id()?)?;
1313 Some(DefDocs::Module(TidyModuleDocs {
1314 docs: ei.module_docstring.docs.clone().unwrap_or_default(),
1315 }))
1316 }
1317 DefKind::Reference => None,
1318 }
1319 }
1320
1321 pub(crate) fn sig_of_type(self: &Arc<Self>, ti: &TypeInfo, ty: Ty) -> Option<Signature> {
1322 super::sig_of_type(self, ti, ty)
1323 }
1324
1325 pub(crate) fn sig_of_type_or_dyn(
1326 self: &Arc<Self>,
1327 ti: &TypeInfo,
1328 callee_ty: Ty,
1329 callee: &SyntaxNode,
1330 ) -> Option<Signature> {
1331 self.sig_of_type(ti, callee_ty).or_else(|| {
1332 self.analyze_expr(callee).iter().find_map(|(value, _)| {
1333 let Value::Func(callee) = value else {
1334 return None;
1335 };
1336
1337 analyze_signature(self, SignatureTarget::Runtime(callee.clone()))
1339 })
1340 })
1341 }
1342
1343 pub fn analyze_import(&self, source: &SyntaxNode) -> (Option<Value>, Option<Value>) {
1350 if let Some(v) = source.cast::<ast::Expr>().and_then(Self::const_eval) {
1351 return (Some(v), None);
1352 }
1353 let token = &self.analysis.workers.import;
1354 token.enter(|| analyze_import_(self.world(), source))
1355 }
1356
1357 pub fn analyze_expr(&self, source: &SyntaxNode) -> EcoVec<(Value, Option<Styles>)> {
1359 let token = &self.analysis.workers.expression;
1360 token.enter(|| analyze_expr_(self.world(), source))
1361 }
1362
1363 pub fn analyze_bib(&self, introspector: &dyn Introspector) -> Option<Arc<BibInfo>> {
1365 let world = self.world();
1366 let world = (world as &dyn World).track();
1367
1368 analyze_bib(world, introspector.track())
1369 }
1370
1371 pub fn tooltip(&self, source: &Source, cursor: usize) -> Option<Tooltip> {
1377 let token = &self.analysis.workers.tooltip;
1378 token.enter(|| tooltip_(self.world(), source, cursor))
1379 }
1380
1381 pub fn get_manifest(&self, toml_id: TypstFileId) -> StrResult<PackageManifest> {
1383 crate::package::get_manifest(self.world(), toml_id)
1384 }
1385
1386 pub fn compute_signature(
1388 self: &Arc<Self>,
1389 func: SignatureTarget,
1390 compute: impl FnOnce(&Arc<Self>) -> Option<Signature> + Send + Sync + 'static,
1391 ) -> Option<Signature> {
1392 let res = match func {
1393 SignatureTarget::Def(src, def) => self
1394 .analysis
1395 .caches
1396 .def_signatures
1397 .entry(hash128(&(src, def.clone())), self.lifetime),
1398 SignatureTarget::SyntaxFast(source, span) => {
1399 let cache_key = (source, span, true);
1400 self.analysis
1401 .caches
1402 .static_signatures
1403 .entry(hash128(&cache_key), self.lifetime)
1404 }
1405 SignatureTarget::Syntax(source, span) => {
1406 let cache_key = (source, span);
1407 self.analysis
1408 .caches
1409 .static_signatures
1410 .entry(hash128(&cache_key), self.lifetime)
1411 }
1412 SignatureTarget::Convert(rt) => self
1413 .analysis
1414 .caches
1415 .signatures
1416 .entry(hash128(&(&rt, true)), self.lifetime),
1417 SignatureTarget::Runtime(rt) => self
1418 .analysis
1419 .caches
1420 .signatures
1421 .entry(hash128(&rt), self.lifetime),
1422 };
1423 res.get_or_init(|| compute(self)).clone()
1424 }
1425
1426 pub(crate) fn compute_docstring(
1427 self: &Arc<Self>,
1428 fid: TypstFileId,
1429 docs: String,
1430 kind: DefKind,
1431 ) -> Option<Arc<DocString>> {
1432 let res = self
1433 .analysis
1434 .caches
1435 .docstrings
1436 .entry(hash128(&(fid, &docs, kind)), self.lifetime);
1437 res.get_or_init(|| {
1438 crate::syntax::docs::do_compute_docstring(self, fid, docs, kind).map(Arc::new)
1439 })
1440 .clone()
1441 }
1442
1443 pub fn remove_html(&self, markup: EcoString) -> EcoString {
1445 if !self.analysis.remove_html {
1446 return markup;
1447 }
1448
1449 static REMOVE_HTML_COMMENT_REGEX: LazyLock<regex::Regex> =
1450 LazyLock::new(|| regex::Regex::new(r#"<!--[\s\S]*?-->"#).unwrap());
1451 REMOVE_HTML_COMMENT_REGEX
1452 .replace_all(&markup, "")
1453 .trim()
1454 .into()
1455 }
1456
1457 pub(super) fn query_stat(&self, id: TypstFileId, query: &'static str) -> QueryStatGuard {
1458 self.analysis.stats.stat(Some(id), query)
1459 }
1460
1461 pub(super) fn expr_stage_stat(&self, id: TypstFileId) -> QueryStatGuard {
1463 self.query_stat(id, "expr_stage")
1464 }
1465
1466 pub(crate) fn prefetch_type_check(self: &Arc<Self>, _fid: TypstFileId) {
1469 }
1479
1480 pub(crate) fn preload_expr_stages<I>(self: Arc<Self>, files: I)
1481 where
1482 I: IntoIterator<Item = TypstFileId>,
1483 {
1484 let files: Vec<_> = files.into_iter().collect();
1485 files.par_iter().for_each(|fid| {
1486 crate::log_debug_ct!("preload expr_stage {fid:?}");
1487 let Some(source) = self.source_by_id(*fid).ok() else {
1488 return;
1489 };
1490 self.expr_stage(&source);
1491 });
1492 }
1493
1494 pub(crate) fn preload_package(self: Arc<Self>, entry_point: TypstFileId) {
1495 crate::log_debug_ct!("preload package start {entry_point:?}");
1496
1497 #[derive(Clone)]
1498 struct Preloader {
1499 shared: Arc<SharedContext>,
1500 analyzed: Arc<Mutex<HashSet<TypstFileId>>>,
1501 }
1502
1503 impl Preloader {
1504 fn work(&self, fid: TypstFileId) {
1505 crate::log_debug_ct!("preload package {fid:?}");
1506 let Some(source) = self.shared.source_by_id(fid).ok() else {
1507 return;
1508 };
1509 let exprs = self.shared.expr_stage(&source);
1510 self.shared.type_check(&source);
1511 exprs.imports.iter().for_each(|(fid, _)| {
1512 if !self.analyzed.lock().insert(*fid) {
1513 return;
1514 }
1515 self.work(*fid);
1516 })
1517 }
1518 }
1519
1520 let preloader = Preloader {
1521 shared: self,
1522 analyzed: Arc::new(Mutex::new(HashSet::from([entry_point]))),
1523 };
1524
1525 preloader.work(entry_point);
1526 }
1527}
1528
1529type DeferredCompute<T> = Arc<OnceLock<T>>;
1531
1532#[derive(Clone)]
1533struct IncrCacheMap<K, V> {
1534 revision: usize,
1535 global: Arc<Mutex<FxDashMap<K, (usize, V)>>>,
1536 prev: Arc<Mutex<FxHashMap<K, DeferredCompute<V>>>>,
1537 next: Arc<Mutex<FxHashMap<K, DeferredCompute<V>>>>,
1538}
1539
1540impl<K: Eq + Hash, V> Default for IncrCacheMap<K, V> {
1541 fn default() -> Self {
1542 Self {
1543 revision: 0,
1544 global: Arc::default(),
1545 prev: Arc::default(),
1546 next: Arc::default(),
1547 }
1548 }
1549}
1550
1551impl<K, V> IncrCacheMap<K, V> {
1552 fn previous(&self, key: &K) -> Option<V>
1554 where
1555 K: Clone + Eq + Hash,
1556 V: Clone,
1557 {
1558 let prev = self.prev.lock().get(key).cloned();
1559 let prev = prev.and_then(|prev| prev.get().cloned());
1560 prev.or_else(|| {
1561 let global = self.global.lock();
1562 global
1563 .get(key)
1564 .filter(|global| global.0 <= self.revision)
1565 .map(|global| global.1.clone())
1566 })
1567 }
1568
1569 fn publish(&self, key: K, value: V) -> V
1571 where
1572 K: Clone + Eq + Hash,
1573 V: Clone,
1574 {
1575 let next = self.next.lock().entry(key.clone()).or_default().clone();
1576 next.get_or_init(|| {
1577 self.publish_global(key, value.clone());
1578 value
1579 })
1580 .clone()
1581 }
1582
1583 fn compute(&self, key: K, compute: impl FnOnce(Option<V>) -> V) -> V
1584 where
1585 K: Clone + Eq + Hash,
1586 V: Clone,
1587 {
1588 let next = self.next.lock().entry(key.clone()).or_default().clone();
1589
1590 next.get_or_init(|| self.compute_and_publish(key, compute))
1591 .clone()
1592 }
1593
1594 fn compute_and_publish(&self, key: K, compute: impl FnOnce(Option<V>) -> V) -> V
1595 where
1596 K: Clone + Eq + Hash,
1597 V: Clone,
1598 {
1599 let res = compute(self.previous(&key));
1600 self.publish_global(key, res.clone());
1601 res
1602 }
1603
1604 fn publish_global(&self, key: K, value: V)
1605 where
1606 K: Clone + Eq + Hash,
1607 V: Clone,
1608 {
1609 let global = self.global.lock();
1610 let entry = global.entry(key);
1611 use dashmap::mapref::entry::Entry;
1612 match entry {
1613 Entry::Occupied(mut entry) => {
1614 let (revision, _) = entry.get();
1615 if *revision < self.revision {
1616 entry.insert((self.revision, value));
1617 }
1618 }
1619 Entry::Vacant(entry) => {
1620 entry.insert((self.revision, value));
1621 }
1622 }
1623 }
1624
1625 fn crawl(&self, revision: usize) -> Self {
1626 Self {
1627 revision,
1628 prev: self.next.clone(),
1629 global: self.global.clone(),
1630 next: Default::default(),
1631 }
1632 }
1633}
1634
1635#[cfg(test)]
1636mod incr_cache_tests {
1637 use super::IncrCacheMap;
1638
1639 #[test]
1640 fn panicked_initializer_leaves_slot_empty() {
1641 let cache = IncrCacheMap::<u8, u8>::default();
1642 let result = std::panic::catch_unwind(std::panic::AssertUnwindSafe(|| {
1643 cache.compute(1, |_| panic!("test panic"))
1644 }));
1645
1646 assert!(result.is_err());
1647 assert_eq!(cache.compute(1, |_| 2), 2);
1648 }
1649
1650 #[test]
1651 fn older_revision_does_not_read_newer_global_value() {
1652 let cache = IncrCacheMap::<u8, u8>::default();
1653 let newer = cache.crawl(2);
1654 assert_eq!(newer.compute(1, |_| 20), 20);
1655
1656 let older = cache.crawl(1);
1657 assert_eq!(
1658 older.compute(1, |prev| {
1659 assert_eq!(prev, None);
1660 10
1661 }),
1662 10
1663 );
1664 assert_eq!(
1665 older.compute(1, |_| panic!("completed value must be reused")),
1666 10
1667 );
1668 }
1669}
1670
1671#[derive(Clone)]
1672struct CacheMap<T> {
1673 m: Arc<FxDashMap<u128, (u64, T)>>,
1674 }
1676
1677impl<T> Default for CacheMap<T> {
1678 fn default() -> Self {
1679 Self {
1680 m: Default::default(),
1681 }
1683 }
1684}
1685
1686impl<T> CacheMap<T> {
1687 fn clear(&self) {
1688 self.m.clear();
1689 }
1690
1691 fn retain(&self, mut f: impl FnMut(&mut (u64, T)) -> bool) {
1692 self.m.retain(|_k, v| f(v));
1693 }
1694}
1695
1696impl<T: Default + Clone> CacheMap<T> {
1697 fn entry(&self, key: u128, lifetime: u64) -> T {
1698 let entry = self.m.entry(key);
1699 let entry = entry.or_insert_with(|| (lifetime, T::default()));
1700 entry.1.clone()
1701 }
1702}
1703
1704#[derive(Default)]
1706pub struct AnalysisGlobalWorkers {
1707 import: RateLimiter,
1709 expression: RateLimiter,
1711 tooltip: RateLimiter,
1713}
1714
1715#[derive(Default, Clone)]
1718pub struct AnalysisGlobalCaches {
1719 lifetime: Arc<AtomicU64>,
1720 clear_lifetime: Arc<AtomicU64>,
1721 def_signatures: CacheMap<DeferredCompute<Option<Signature>>>,
1722 static_signatures: CacheMap<DeferredCompute<Option<Signature>>>,
1723 signatures: CacheMap<DeferredCompute<Option<Signature>>>,
1724 docstrings: CacheMap<DeferredCompute<Option<Arc<DocString>>>>,
1725 terms: CacheMap<(Value, Ty)>,
1726}
1727
1728#[derive(Default)]
1734pub struct AnalysisLocalCaches {
1735 modules: HashMap<TypstFileId, ModuleAnalysisLocalCache>,
1736 completion_files: OnceLock<Vec<TypstFileId>>,
1737 root_files: OnceLock<Vec<TypstFileId>>,
1738 module_deps: OnceLock<HashMap<TypstFileId, ModuleDependency>>,
1739}
1740
1741#[derive(Default)]
1746pub struct ModuleAnalysisLocalCache {
1747 expr_stage: OnceLock<ExprInfo>,
1748 type_check: OnceLock<Arc<TypeInfo>>,
1749}
1750
1751#[derive(Default)]
1754pub struct AnalysisRevCache {
1755 default_slot: AnalysisRevSlot,
1756 manager: RevisionManager<AnalysisRevSlot>,
1757}
1758
1759impl RevisionManagerLike for AnalysisRevCache {
1760 fn gc(&mut self, rev: usize) {
1761 self.manager.gc(rev);
1762
1763 {
1765 let mut max_ei = FxHashMap::default();
1766 let es = self.default_slot.expr_stage.global.lock();
1767 for r in es.iter() {
1768 let rev: &mut usize = max_ei.entry(r.1.fid).or_default();
1769 *rev = (*rev).max(r.1.revision);
1770 }
1771 es.retain(|_, r| r.1.revision == *max_ei.get(&r.1.fid).unwrap_or(&0));
1772 }
1773
1774 {
1775 let mut max_ti = FxHashMap::default();
1776 let ts = self.default_slot.type_check.global.lock();
1777 for r in ts.iter() {
1778 let rev: &mut usize = max_ti.entry(r.1.fid).or_default();
1779 *rev = (*rev).max(r.1.revision);
1780 }
1781 ts.retain(|_, r| r.1.revision == *max_ti.get(&r.1.fid).unwrap_or(&0));
1782 }
1783
1784 {
1785 let mut max_li = FxHashMap::default();
1786 let ts = self.default_slot.lint.global.lock();
1787 for r in ts.iter() {
1788 let rev: &mut usize = max_li.entry(r.1.fid).or_default();
1789 *rev = (*rev).max(r.1.revision);
1790 }
1791 ts.retain(|_, r| r.1.revision == *max_li.get(&r.1.fid).unwrap_or(&0));
1792 }
1793 }
1794}
1795
1796impl AnalysisRevCache {
1797 fn clear(&mut self) {
1798 self.manager.clear();
1799 self.default_slot = Default::default();
1800 }
1801
1802 fn find_revision(
1804 &mut self,
1805 revision: NonZeroUsize,
1806 lg: &AnalysisRevLock,
1807 ) -> Arc<RevisionSlot<AnalysisRevSlot>> {
1808 lg.inner.access(revision);
1809 self.manager.find_revision(revision, |slot_base| {
1810 log::debug!("analysis revision {} is created", revision.get());
1811 slot_base
1812 .map(|slot| slot.data.crawl(revision.get()))
1813 .unwrap_or_else(|| self.default_slot.crawl(revision.get()))
1814 })
1815 }
1816}
1817
1818pub struct AnalysisRevLock {
1820 inner: RevisionLock,
1821 tokens: Option<SemanticTokenContext>,
1822 grid: Arc<Mutex<AnalysisRevCache>>,
1823}
1824
1825impl Drop for AnalysisRevLock {
1826 fn drop(&mut self) {
1827 let mut mu = self.grid.lock();
1828 let gc_revision = mu.manager.unlock(&mut self.inner);
1829
1830 if let Some(gc_revision) = gc_revision {
1831 let grid = self.grid.clone();
1832 rayon::spawn(move || {
1833 grid.lock().gc(gc_revision);
1834 });
1835 }
1836 }
1837}
1838
1839#[derive(Default)]
1840struct AnalysisRevSlot {
1841 revision: usize,
1842 components: ComponentCoordinator,
1843 expr_stage: IncrCacheMap<u128, ExprInfo>,
1844 type_check: IncrCacheMap<u128, Arc<TypeInfo>>,
1845 lint: IncrCacheMap<u128, LintInfo>,
1846}
1847
1848impl AnalysisRevSlot {
1849 fn crawl(&self, revision: usize) -> Self {
1850 Self {
1851 revision,
1852 components: ComponentCoordinator::default(),
1854 expr_stage: self.expr_stage.crawl(revision),
1855 type_check: self.type_check.crawl(revision),
1856 lint: self.lint.crawl(revision),
1857 }
1858 }
1859}
1860
1861impl Drop for AnalysisRevSlot {
1862 fn drop(&mut self) {
1863 log::debug!("analysis revision {} is dropped", self.revision);
1864 }
1865}
1866
1867fn ceil_char_boundary(text: &str, mut cursor: usize) -> usize {
1868 while cursor < text.len() && !text.is_char_boundary(cursor) {
1870 cursor += 1;
1871 }
1872
1873 cursor.min(text.len())
1874}
1875
1876#[typst_macros::time]
1877#[comemo::memoize]
1878fn analyze_bib(
1879 world: Tracked<dyn World + '_>,
1880 introspector: Tracked<dyn Introspector + '_>,
1881) -> Option<Arc<BibInfo>> {
1882 let bib_elems = introspector.query(&BibliographyElem::ELEM.select());
1883 let bib_elem = bib_elems.iter().next()?.to_packed::<BibliographyElem>()?;
1884
1885 let csl_style = bib_elem.style.get_cloned(StyleChain::default()).derived;
1888
1889 let Value::Array(paths) = bib_elem.sources.clone().into_value() else {
1890 return None;
1891 };
1892 let elem_fid = bib_elem.span().id()?;
1893 let files = paths
1894 .into_iter()
1895 .flat_map(|path| path.cast().ok())
1896 .flat_map(|bib_path: EcoString| {
1897 let bib_fid = resolve_id_by_path(world.deref(), elem_fid, &bib_path)?;
1898 Some((bib_fid, world.file(bib_fid).ok()?))
1899 });
1900
1901 bib_info(csl_style, files)
1902}
1903
1904#[comemo::memoize]
1905fn loc_info(bytes: Bytes) -> Option<EcoVec<(usize, String)>> {
1906 let mut loc = EcoVec::new();
1907 let mut offset = 0;
1908 for line in bytes.split(|byte| *byte == b'\n') {
1909 loc.push((offset, String::from_utf8(line.to_owned()).ok()?));
1910 offset += line.len() + 1;
1911 }
1912 Some(loc)
1913}
1914
1915fn find_loc(
1916 len: usize,
1917 loc: &EcoVec<(usize, String)>,
1918 mut offset: usize,
1919 encoding: PositionEncoding,
1920) -> Option<LspPosition> {
1921 if offset > len {
1922 offset = len;
1923 }
1924
1925 let r = match loc.binary_search_by_key(&offset, |line| line.0) {
1926 Ok(i) => i,
1927 Err(i) => i - 1,
1928 };
1929
1930 let (start, s) = loc.get(r)?;
1931 let byte_offset = offset.saturating_sub(*start);
1932
1933 let column_prefix = if byte_offset <= s.len() {
1934 &s[..byte_offset]
1935 } else {
1936 let line = (r + 1) as u32;
1937 return Some(LspPosition { line, character: 0 });
1938 };
1939
1940 let line = r as u32;
1941 let character = match encoding {
1942 PositionEncoding::Utf8 => column_prefix.chars().count(),
1943 PositionEncoding::Utf16 => column_prefix.chars().map(|ch| ch.len_utf16()).sum(),
1944 } as u32;
1945
1946 Some(LspPosition { line, character })
1947}
1948
1949pub struct SearchCtx<'a> {
1951 pub ctx: &'a mut LocalContext,
1953 pub searched: HashSet<TypstFileId>,
1955 pub worklist: Vec<TypstFileId>,
1957}
1958
1959impl SearchCtx<'_> {
1960 pub fn push(&mut self, fid: TypstFileId) -> bool {
1962 if self.searched.insert(fid) {
1963 self.worklist.push(fid);
1964 true
1965 } else {
1966 false
1967 }
1968 }
1969
1970 pub fn push_dependents(&mut self, fid: TypstFileId) {
1972 let deps = self.ctx.module_dependencies().get(&fid);
1973 let dependents = deps.map(|dep| dep.dependents.clone()).into_iter().flatten();
1974 for dep in dependents {
1975 self.push(dep);
1976 }
1977 }
1978}
1979
1980#[derive(Default)]
1982pub struct RateLimiter {
1983 token: std::sync::Mutex<()>,
1984}
1985
1986impl RateLimiter {
1987 #[must_use]
1989 pub fn enter<T>(&self, f: impl FnOnce() -> T) -> T {
1990 let _c = self.token.lock().unwrap();
1991 f()
1992 }
1993}