1use std::num::NonZeroUsize;
2use std::ops::DerefMut;
3use std::sync::OnceLock;
4use std::sync::atomic::{AtomicU64, Ordering};
5use std::{collections::HashSet, ops::Deref};
6
7use comemo::{Track, Tracked};
8use lsp_types::Url;
9use parking_lot::Mutex;
10use rustc_hash::FxHashMap;
11use tinymist_analysis::docs::DocString;
12use tinymist_analysis::stats::AllocStats;
13use tinymist_analysis::syntax::classify_def_loosely;
14use tinymist_analysis::ty::{BuiltinTy, InsTy, term_value};
15use tinymist_analysis::{analyze_expr_, analyze_import_};
16use tinymist_lint::{KnownIssues, LintInfo};
17use tinymist_project::{LspComputeGraph, LspWorld, TaskWhen};
18use tinymist_std::hash::{FxDashMap, hash128};
19use tinymist_std::typst::TypstDocument;
20use tinymist_world::debug_loc::DataSource;
21use tinymist_world::vfs::{PathResolution, WorkspaceResolver};
22use tinymist_world::{DETACHED_ENTRY, EntryReader};
23use typst::diag::{At, FileError, FileResult, SourceDiagnostic, SourceResult, StrResult};
24use typst::foundations::{Bytes, IntoValue, Module, StyleChain, Styles};
25use typst::introspection::Introspector;
26use typst::layout::Position;
27use typst::model::BibliographyElem;
28use typst::syntax::package::{PackageManifest, PackageSpec};
29use typst::syntax::{Span, VirtualPath};
30use typst_shim::eval::{Eval, eval_compat};
31
32use super::{LspQuerySnapshot, TypeEnv};
33use crate::adt::revision::{RevisionLock, RevisionManager, RevisionManagerLike, RevisionSlot};
34use crate::analysis::prelude::*;
35use crate::analysis::{
36 AnalysisStats, BibInfo, CompletionFeat, Definition, PathKind, QueryStatGuard,
37 SemanticTokenCache, SemanticTokenContext, SemanticTokens, Signature, SignatureTarget, Ty,
38 TypeInfo, analyze_signature, bib_info, definition, post_type_check,
39};
40use crate::docs::{DefDocs, TidyModuleDocs};
41use crate::syntax::{
42 Decl, DefKind, ExprInfo, ExprRoute, LexicalScope, ModuleDependency, SyntaxClass,
43 classify_syntax, construct_module_dependencies, is_mark, resolve_id_by_path,
44 scan_workspace_files,
45};
46use crate::upstream::{Tooltip, tooltip_};
47use crate::{
48 ColorTheme, CompilerQueryRequest, LspPosition, LspRange, LspWorldExt, PositionEncoding,
49};
50
51macro_rules! interned_str {
52 ($name:ident, $value:expr) => {
53 static $name: LazyLock<Interned<str>> = LazyLock::new(|| $value.into());
54 };
55}
56
57#[derive(Default, Clone)]
59pub struct Analysis {
60 pub position_encoding: PositionEncoding,
62 pub allow_overlapping_token: bool,
64 pub allow_multiline_token: bool,
66 pub remove_html: bool,
68 pub extended_code_action: bool,
76 pub completion_feat: CompletionFeat,
78 pub color_theme: ColorTheme,
80 pub lint: TaskWhen,
82 pub periscope: Option<Arc<dyn PeriscopeProvider + Send + Sync>>,
84 pub workers: Arc<AnalysisGlobalWorkers>,
86 pub tokens_caches: Arc<Mutex<SemanticTokenCache>>,
88 pub caches: AnalysisGlobalCaches,
90 pub analysis_rev_cache: Arc<Mutex<AnalysisRevCache>>,
92 pub stats: Arc<AnalysisStats>,
94}
95
96impl Analysis {
97 pub fn enter(&self, world: LspWorld) -> LocalContextGuard {
99 self.enter_(world, self.lock_revision(None))
100 }
101
102 pub(crate) fn enter_(&self, world: LspWorld, mut lg: AnalysisRevLock) -> LocalContextGuard {
104 let lifetime = self.caches.lifetime.fetch_add(1, Ordering::SeqCst);
105 let slot = self
106 .analysis_rev_cache
107 .lock()
108 .find_revision(world.revision(), &lg);
109 let tokens = lg.tokens.take();
110 LocalContextGuard {
111 _rev_lock: lg,
112 local: LocalContext {
113 tokens,
114 caches: AnalysisLocalCaches::default(),
115 shared: Arc::new(SharedContext {
116 slot,
117 lifetime,
118 world,
119 analysis: self.clone(),
120 }),
121 },
122 }
123 }
124
125 pub fn query_snapshot(
127 self: Arc<Self>,
128 snap: LspComputeGraph,
129 req: Option<&CompilerQueryRequest>,
130 ) -> LspQuerySnapshot {
131 let rev_lock = self.lock_revision(req);
132 LspQuerySnapshot {
133 snap,
134 analysis: self,
135 rev_lock,
136 }
137 }
138
139 #[must_use]
141 pub fn lock_revision(&self, req: Option<&CompilerQueryRequest>) -> AnalysisRevLock {
142 let mut grid = self.analysis_rev_cache.lock();
143
144 AnalysisRevLock {
145 tokens: match req {
146 Some(CompilerQueryRequest::SemanticTokensFull(req)) => Some(
147 SemanticTokenCache::acquire(self.tokens_caches.clone(), &req.path, None),
148 ),
149 Some(CompilerQueryRequest::SemanticTokensDelta(req)) => {
150 Some(SemanticTokenCache::acquire(
151 self.tokens_caches.clone(),
152 &req.path,
153 Some(&req.previous_result_id),
154 ))
155 }
156 _ => None,
157 },
158 inner: grid.manager.lock_estimated(),
159 grid: self.analysis_rev_cache.clone(),
160 }
161 }
162
163 pub fn clear_cache(&self) {
165 self.caches.signatures.clear();
166 self.caches.docstrings.clear();
167 self.caches.def_signatures.clear();
168 self.caches.static_signatures.clear();
169 self.caches.terms.clear();
170 self.tokens_caches.lock().clear();
171 self.analysis_rev_cache.lock().clear();
172 }
173
174 pub fn report_query_stats(&self) -> String {
176 self.stats.report()
177 }
178
179 pub fn report_alloc_stats(&self) -> String {
181 AllocStats::report()
182 }
183
184 pub fn trigger_suggest(&self, context: bool) -> Option<Interned<str>> {
186 interned_str!(INTERNED, "editor.action.triggerSuggest");
187
188 (self.completion_feat.trigger_suggest && context).then(|| INTERNED.clone())
189 }
190
191 pub fn trigger_parameter_hints(&self, context: bool) -> Option<Interned<str>> {
193 interned_str!(INTERNED, "editor.action.triggerParameterHints");
194 (self.completion_feat.trigger_parameter_hints && context).then(|| INTERNED.clone())
195 }
196
197 pub fn trigger_on_snippet(&self, context: bool) -> Option<Interned<str>> {
204 if !self.completion_feat.trigger_on_snippet_placeholders {
205 return None;
206 }
207
208 self.trigger_suggest(context)
209 }
210
211 pub fn trigger_on_snippet_with_param_hint(&self, context: bool) -> Option<Interned<str>> {
213 interned_str!(INTERNED, "tinymist.triggerSuggestAndParameterHints");
214 if !self.completion_feat.trigger_on_snippet_placeholders {
215 return self.trigger_parameter_hints(context);
216 }
217
218 (self.completion_feat.trigger_suggest_and_parameter_hints && context)
219 .then(|| INTERNED.clone())
220 }
221}
222
223pub trait PeriscopeProvider {
225 fn periscope_at(
227 &self,
228 _ctx: &mut LocalContext,
229 _doc: &TypstDocument,
230 _pos: Position,
231 ) -> Option<String> {
232 None
233 }
234}
235
236pub struct LocalContextGuard {
238 pub local: LocalContext,
240 _rev_lock: AnalysisRevLock,
242}
243
244impl Deref for LocalContextGuard {
245 type Target = LocalContext;
246
247 fn deref(&self) -> &Self::Target {
248 &self.local
249 }
250}
251
252impl DerefMut for LocalContextGuard {
253 fn deref_mut(&mut self) -> &mut Self::Target {
254 &mut self.local
255 }
256}
257
258impl Drop for LocalContextGuard {
260 fn drop(&mut self) {
261 self.gc();
262 }
263}
264
265impl LocalContextGuard {
266 fn gc(&self) {
267 let lifetime = self.lifetime;
268 loop {
269 let latest_clear_lifetime = self.analysis.caches.clear_lifetime.load(Ordering::Relaxed);
270 if latest_clear_lifetime >= lifetime {
271 return;
272 }
273
274 if self.analysis.caches.clear_lifetime.compare_exchange(
275 latest_clear_lifetime,
276 lifetime,
277 Ordering::SeqCst,
278 Ordering::SeqCst,
279 ) != Ok(latest_clear_lifetime)
280 {
281 continue;
282 }
283
284 break;
285 }
286
287 let retainer = |l: u64| lifetime.saturating_sub(l) < 60;
288 let caches = &self.analysis.caches;
289 caches.def_signatures.retain(|(l, _)| retainer(*l));
290 caches.static_signatures.retain(|(l, _)| retainer(*l));
291 caches.terms.retain(|(l, _)| retainer(*l));
292 caches.signatures.retain(|(l, _)| retainer(*l));
293 caches.docstrings.retain(|(l, _)| retainer(*l));
294 }
295}
296
297pub struct LocalContext {
300 pub(crate) tokens: Option<SemanticTokenContext>,
302 pub caches: AnalysisLocalCaches,
304 pub shared: Arc<SharedContext>,
306}
307
308impl Deref for LocalContext {
309 type Target = Arc<SharedContext>;
310
311 fn deref(&self) -> &Self::Target {
312 &self.shared
313 }
314}
315
316impl DerefMut for LocalContext {
317 fn deref_mut(&mut self) -> &mut Self::Target {
318 &mut self.shared
319 }
320}
321
322impl LocalContext {
323 #[cfg(test)]
325 pub fn test_package_list(&mut self, f: impl FnOnce() -> Vec<(PackageSpec, Option<EcoString>)>) {
326 self.world.registry.test_package_list(f);
327 }
328
329 #[cfg(test)]
331 pub fn test_completion_files(&mut self, f: impl FnOnce() -> Vec<TypstFileId>) {
332 self.caches.completion_files.get_or_init(f);
333 }
334
335 #[cfg(test)]
337 pub fn test_files(&mut self, f: impl FnOnce() -> Vec<TypstFileId>) {
338 self.caches.root_files.get_or_init(f);
339 }
340
341 pub(crate) fn completion_files(&self, pref: &PathKind) -> impl Iterator<Item = &TypstFileId> {
343 let regexes = pref.ext_matcher();
344 self.caches
345 .completion_files
346 .get_or_init(|| {
347 if let Some(root) = self.world.entry_state().workspace_root() {
348 scan_workspace_files(&root, PathKind::Special.ext_matcher(), |path| {
349 WorkspaceResolver::workspace_file(Some(&root), VirtualPath::new(path))
350 })
351 } else {
352 vec![]
353 }
354 })
355 .iter()
356 .filter(move |fid| {
357 fid.vpath()
358 .as_rooted_path()
359 .extension()
360 .and_then(|path| path.to_str())
361 .is_some_and(|path| regexes.is_match(path))
362 })
363 }
364
365 pub fn source_files(&self) -> &Vec<TypstFileId> {
367 self.caches.root_files.get_or_init(|| {
368 self.completion_files(&PathKind::Source {
369 allow_package: false,
370 })
371 .copied()
372 .collect()
373 })
374 }
375
376 pub fn module_dependencies(&mut self) -> &HashMap<TypstFileId, ModuleDependency> {
378 if self.caches.module_deps.get().is_some() {
379 self.caches.module_deps.get().unwrap()
380 } else {
381 let deps = construct_module_dependencies(self);
384 self.caches.module_deps.get_or_init(|| deps)
385 }
386 }
387
388 pub fn depended_source_files(&self) -> EcoVec<TypstFileId> {
390 let mut ids = self.depended_files();
391 let preference = PathKind::Source {
392 allow_package: false,
393 };
394 ids.retain(|id| preference.is_match(id.vpath().as_rooted_path()));
395 ids
396 }
397
398 pub fn depended_files(&self) -> EcoVec<TypstFileId> {
401 self.world.depended_files()
402 }
403
404 pub fn world(&self) -> &LspWorld {
406 &self.shared.world
407 }
408
409 pub fn shared(&self) -> &Arc<SharedContext> {
411 &self.shared
412 }
413
414 pub fn shared_(&self) -> Arc<SharedContext> {
416 self.shared.clone()
417 }
418
419 pub fn fork_for_search(&mut self) -> SearchCtx<'_> {
421 SearchCtx {
422 ctx: self,
423 searched: Default::default(),
424 worklist: Default::default(),
425 }
426 }
427
428 pub(crate) fn preload_package(&self, entry_point: TypstFileId) {
429 self.shared_().preload_package(entry_point);
430 }
431
432 pub(crate) fn with_vm<T>(&self, f: impl FnOnce(&mut typst_shim::eval::Vm) -> T) -> T {
433 crate::upstream::with_vm((self.world() as &dyn World).track(), f)
434 }
435
436 pub(crate) fn const_eval(&self, rr: ast::Expr<'_>) -> Option<Value> {
437 SharedContext::const_eval(rr)
438 }
439
440 pub(crate) fn mini_eval(&self, rr: ast::Expr<'_>) -> Option<Value> {
441 self.const_eval(rr)
442 .or_else(|| self.with_vm(|vm| rr.eval(vm).ok()))
443 }
444
445 pub(crate) fn cached_tokens(&mut self, source: &Source) -> (SemanticTokens, Option<String>) {
446 let tokens = crate::analysis::semantic_tokens::get_semantic_tokens(self, source);
447
448 let result_id = self.tokens.as_ref().map(|t| {
449 let id = t.next.revision;
450 t.next
451 .data
452 .set(tokens.clone())
453 .unwrap_or_else(|_| panic!("unexpected slot overwrite {id}"));
454 id.to_string()
455 });
456 (tokens, result_id)
457 }
458
459 pub(crate) fn expr_stage_by_id(&mut self, fid: TypstFileId) -> Option<ExprInfo> {
461 Some(self.expr_stage(&self.source_by_id(fid).ok()?))
462 }
463
464 pub(crate) fn expr_stage(&mut self, source: &Source) -> ExprInfo {
466 let id = source.id();
467 let cache = &self.caches.modules.entry(id).or_default().expr_stage;
468 cache.get_or_init(|| self.shared.expr_stage(source)).clone()
469 }
470
471 pub(crate) fn type_check(&mut self, source: &Source) -> Arc<TypeInfo> {
473 let id = source.id();
474 let cache = &self.caches.modules.entry(id).or_default().type_check;
475 cache.get_or_init(|| self.shared.type_check(source)).clone()
476 }
477
478 pub(crate) fn lint(
479 &mut self,
480 source: &Source,
481 known_issues: &KnownIssues,
482 ) -> EcoVec<SourceDiagnostic> {
483 self.shared.lint(source, known_issues).diagnostics
484 }
485
486 pub(crate) fn type_check_by_id(&mut self, id: TypstFileId) -> Arc<TypeInfo> {
488 let cache = &self.caches.modules.entry(id).or_default().type_check;
489 cache
490 .clone()
491 .get_or_init(|| {
492 let source = self.source_by_id(id).ok();
493 source
494 .map(|s| self.shared.type_check(&s))
495 .unwrap_or_default()
496 })
497 .clone()
498 }
499
500 pub(crate) fn type_of_span(&mut self, s: Span) -> Option<Ty> {
501 let scheme = self.type_check_by_id(s.id()?);
502 let ty = scheme.type_of_span(s)?;
503 Some(scheme.simplify(ty, false))
504 }
505
506 pub(crate) fn def_docs(&mut self, def: &Definition) -> Option<DefDocs> {
507 match def.decl.kind() {
510 DefKind::Function => {
511 let sig = self.sig_of_def(def.clone())?;
512 let docs = crate::docs::sig_docs(&sig)?;
513 Some(DefDocs::Function(Box::new(docs)))
514 }
515 DefKind::Struct | DefKind::Constant | DefKind::Variable => {
516 let docs = crate::docs::var_docs(self, def.decl.span())?;
517 Some(DefDocs::Variable(docs))
518 }
519 DefKind::Module => {
520 let ei = self.expr_stage_by_id(def.decl.file_id()?)?;
521 Some(DefDocs::Module(TidyModuleDocs {
522 docs: ei.module_docstring.docs.clone().unwrap_or_default(),
523 }))
524 }
525 DefKind::Reference => None,
526 }
527 }
528}
529
530pub struct SharedContext {
532 pub lifetime: u64,
534 pub world: LspWorld,
536 pub analysis: Analysis,
538 slot: Arc<RevisionSlot<AnalysisRevSlot>>,
540}
541
542impl SharedContext {
543 pub fn revision(&self) -> usize {
545 self.slot.revision
546 }
547
548 pub(crate) fn position_encoding(&self) -> PositionEncoding {
550 self.analysis.position_encoding
551 }
552
553 pub fn to_typst_pos(&self, position: LspPosition, src: &Source) -> Option<usize> {
555 crate::to_typst_position(position, self.analysis.position_encoding, src)
556 }
557
558 pub fn to_typst_pos_offset(
560 &self,
561 source: &Source,
562 position: LspPosition,
563 shift: usize,
564 ) -> Option<usize> {
565 let offset = self.to_typst_pos(position, source)?;
566 Some(ceil_char_boundary(source.text(), offset + shift))
567 }
568
569 pub fn to_lsp_pos(&self, typst_offset: usize, src: &Source) -> LspPosition {
571 crate::to_lsp_position(typst_offset, self.analysis.position_encoding, src)
572 }
573
574 pub fn to_typst_range(&self, position: LspRange, src: &Source) -> Option<Range<usize>> {
576 crate::to_typst_range(position, self.analysis.position_encoding, src)
577 }
578
579 pub fn to_lsp_range(&self, position: Range<usize>, src: &Source) -> LspRange {
581 crate::to_lsp_range(position, src, self.analysis.position_encoding)
582 }
583
584 pub fn to_lsp_range_(&self, position: Range<usize>, fid: TypstFileId) -> Option<LspRange> {
586 let ext = fid
587 .vpath()
588 .as_rootless_path()
589 .extension()
590 .and_then(|ext| ext.to_str());
591 if matches!(ext, Some("yaml" | "yml" | "bib")) {
593 let bytes = self.file_by_id(fid).ok()?;
594 let bytes_len = bytes.len();
595 let loc = loc_info(bytes)?;
596 let start = find_loc(bytes_len, &loc, position.start, self.position_encoding())?;
598 let end = find_loc(bytes_len, &loc, position.end, self.position_encoding())?;
599 return Some(LspRange { start, end });
600 }
601
602 let source = self.source_by_id(fid).ok()?;
603
604 Some(self.to_lsp_range(position, &source))
605 }
606
607 pub fn path_for_id(&self, id: TypstFileId) -> Result<PathResolution, FileError> {
609 self.world.path_for_id(id)
610 }
611
612 pub fn uri_for_id(&self, fid: TypstFileId) -> Result<Url, FileError> {
614 self.world.uri_for_id(fid)
615 }
616
617 pub fn file_id_by_path(&self, path: &Path) -> FileResult<TypstFileId> {
619 self.world.file_id_by_path(path)
620 }
621
622 pub fn file_by_id(&self, fid: TypstFileId) -> FileResult<Bytes> {
624 self.world.file(fid)
625 }
626
627 pub fn source_by_id(&self, fid: TypstFileId) -> FileResult<Source> {
629 self.world.source(fid)
630 }
631
632 pub fn source_by_path(&self, path: &Path) -> FileResult<Source> {
634 self.source_by_id(self.file_id_by_path(path)?)
635 }
636
637 pub fn classify_span<'s>(&self, source: &'s Source, span: Span) -> Option<SyntaxClass<'s>> {
640 let node = LinkedNode::new(source.root()).find(span)?;
641 let cursor = node.offset() + 1;
642 classify_syntax(node, cursor)
643 }
644
645 pub fn classify_for_decl<'s>(
649 &self,
650 source: &'s Source,
651 position: LspPosition,
652 ) -> Option<SyntaxClass<'s>> {
653 let cursor = self.to_typst_pos_offset(source, position, 1)?;
654 let mut node = LinkedNode::new(source.root()).leaf_at_compat(cursor)?;
655
656 if cursor == node.offset() + 1 && is_mark(node.kind()) {
659 let prev_leaf = node.prev_leaf();
660 if let Some(prev_leaf) = prev_leaf
661 && prev_leaf.range().end == node.offset()
662 {
663 node = prev_leaf;
664 }
665 }
666
667 classify_syntax(node, cursor)
668 }
669
670 pub fn font_info(&self, font: typst::text::Font) -> Option<Arc<DataSource>> {
672 self.world.font_resolver.describe_font(&font)
673 }
674
675 #[cfg(feature = "local-registry")]
677 pub fn local_packages(&self) -> EcoVec<PackageSpec> {
678 crate::package::list_package_by_namespace(&self.world.registry, eco_format!("local"))
679 .into_iter()
680 .map(|(_, spec)| spec)
681 .collect()
682 }
683
684 #[cfg(not(feature = "local-registry"))]
686 pub fn local_packages(&self) -> EcoVec<PackageSpec> {
687 eco_vec![]
688 }
689
690 pub(crate) fn const_eval(rr: ast::Expr<'_>) -> Option<Value> {
691 Some(match rr {
692 ast::Expr::None(_) => Value::None,
693 ast::Expr::Auto(_) => Value::Auto,
694 ast::Expr::Bool(v) => Value::Bool(v.get()),
695 ast::Expr::Int(v) => Value::Int(v.get()),
696 ast::Expr::Float(v) => Value::Float(v.get()),
697 ast::Expr::Numeric(v) => Value::numeric(v.get()),
698 ast::Expr::Str(v) => Value::Str(v.get().into()),
699 _ => return None,
700 })
701 }
702
703 pub fn module_by_id(&self, fid: TypstFileId) -> SourceResult<Module> {
705 let source = self.source_by_id(fid).at(Span::detached())?;
706 self.module_by_src(source)
707 }
708
709 pub fn module_by_str(&self, rr: String) -> Option<Module> {
711 let src = Source::new(*DETACHED_ENTRY, rr);
712 self.module_by_src(src).ok()
713 }
714
715 pub fn module_by_src(&self, source: Source) -> SourceResult<Module> {
717 eval_compat(&self.world, &source)
718 }
719
720 pub fn module_by_syntax(self: &Arc<Self>, source: &SyntaxNode) -> Option<Value> {
722 self.module_term_by_syntax(source, true)
723 .and_then(|ty| ty.value())
724 }
725
726 pub fn module_term_by_syntax(self: &Arc<Self>, source: &SyntaxNode, value: bool) -> Option<Ty> {
729 let (src, scope) = self.analyze_import(source);
730 if let Some(scope) = scope {
731 return Some(match scope {
732 Value::Module(m) if m.file_id().is_some() => {
733 Ty::Builtin(BuiltinTy::Module(Decl::module(m.file_id()?).into()))
734 }
735 scope => Ty::Value(InsTy::new(scope)),
736 });
737 }
738
739 match src {
740 Some(Value::Str(s)) => {
741 let id = resolve_id_by_path(&self.world, source.span().id()?, s.as_str())?;
742
743 Some(if value {
744 Ty::Value(InsTy::new(Value::Module(self.module_by_id(id).ok()?)))
745 } else {
746 Ty::Builtin(BuiltinTy::Module(Decl::module(id).into()))
747 })
748 }
749 _ => None,
750 }
751 }
752
753 pub(crate) fn expr_stage_by_id(self: &Arc<Self>, fid: TypstFileId) -> Option<ExprInfo> {
755 Some(self.expr_stage(&self.source_by_id(fid).ok()?))
756 }
757
758 pub(crate) fn expr_stage(self: &Arc<Self>, source: &Source) -> ExprInfo {
760 let mut route = ExprRoute::default();
761 self.expr_stage_(source, &mut route)
762 }
763
764 pub(crate) fn expr_stage_(
766 self: &Arc<Self>,
767 source: &Source,
768 route: &mut ExprRoute,
769 ) -> ExprInfo {
770 use crate::syntax::expr_of;
771 let guard = self.query_stat(source.id(), "expr_stage");
772 self.slot.expr_stage.compute(hash128(&source), |prev| {
773 expr_of(self.clone(), source.clone(), route, guard, prev)
774 })
775 }
776
777 pub(crate) fn exports_of(
778 self: &Arc<Self>,
779 source: &Source,
780 route: &mut ExprRoute,
781 ) -> Option<Arc<LazyHash<LexicalScope>>> {
782 if let Some(s) = route.get(&source.id()) {
783 return s.clone();
784 }
785
786 Some(self.expr_stage_(source, route).exports.clone())
787 }
788
789 pub(crate) fn type_check(self: &Arc<Self>, source: &Source) -> Arc<TypeInfo> {
791 let mut route = TypeEnv::default();
792 self.type_check_(source, &mut route)
793 }
794
795 pub(crate) fn type_check_(
797 self: &Arc<Self>,
798 source: &Source,
799 route: &mut TypeEnv,
800 ) -> Arc<TypeInfo> {
801 use crate::analysis::type_check;
802
803 let ei = self.expr_stage(source);
804 let guard = self.query_stat(source.id(), "type_check");
805 self.slot.type_check.compute(hash128(&ei), |prev| {
806 if let Some(cache_hint) = prev.filter(|prev| prev.revision == ei.revision) {
808 return cache_hint;
809 }
810
811 guard.miss();
812 type_check(self.clone(), ei, route)
813 })
814 }
815
816 #[typst_macros::time(span = source.root().span())]
818 pub(crate) fn lint(self: &Arc<Self>, source: &Source, issues: &KnownIssues) -> LintInfo {
819 let ei = self.expr_stage(source);
820 let ti = self.type_check(source);
821 let guard = self.query_stat(source.id(), "lint");
822 self.slot.lint.compute(hash128(&(&ei, &ti, issues)), |_| {
823 guard.miss();
824 tinymist_lint::lint_file(&self.world, &ei, ti, issues.clone())
825 })
826 }
827
828 pub(crate) fn type_of_func(self: &Arc<Self>, func: Func) -> Signature {
829 crate::log_debug_ct!("convert runtime func {func:?}");
830 analyze_signature(self, SignatureTarget::Convert(func)).unwrap()
831 }
832
833 pub(crate) fn type_of_value(self: &Arc<Self>, val: &Value) -> Ty {
834 crate::log_debug_ct!("convert runtime value {val:?}");
835
836 let cache_key = val;
838 let cached = self
839 .analysis
840 .caches
841 .terms
842 .m
843 .get(&hash128(&cache_key))
844 .and_then(|slot| (cache_key == &slot.1.0).then_some(slot.1.1.clone()));
845 if let Some(cached) = cached {
846 return cached;
847 }
848
849 let res = term_value(val);
850
851 self.analysis
852 .caches
853 .terms
854 .m
855 .entry(hash128(&cache_key))
856 .or_insert_with(|| (self.lifetime, (cache_key.clone(), res.clone())));
857
858 res
859 }
860
861 pub(crate) fn def_of_span(
863 self: &Arc<Self>,
864 source: &Source,
865 doc: Option<&TypstDocument>,
866 span: Span,
867 ) -> Option<Definition> {
868 let syntax = self.classify_span(source, span)?;
869 definition(self, source, doc, syntax)
870 }
871
872 pub(crate) fn def_of_decl(&self, decl: &Interned<Decl>) -> Option<Definition> {
874 match decl.as_ref() {
875 Decl::Func(..) => Some(Definition::new(decl.clone(), None)),
876 Decl::Module(..) => None,
877 _ => None,
878 }
879 }
880
881 pub(crate) fn def_of_syntax(
886 self: &Arc<Self>,
887 source: &Source,
888 doc: Option<&TypstDocument>,
889 syntax: SyntaxClass,
890 ) -> Option<Definition> {
891 definition(self, source, doc, syntax)
892 }
893
894 pub(crate) fn def_of_syntax_or_dyn(
902 self: &Arc<Self>,
903 source: &Source,
904 doc: Option<&TypstDocument>,
905 syntax: SyntaxClass,
906 ) -> Option<Definition> {
907 let def = self.def_of_syntax(source, doc, syntax.clone());
908 match def.as_ref().map(|d| d.decl.kind()) {
909 Some(DefKind::Reference | DefKind::Module | DefKind::Function) => return def,
911 Some(DefKind::Struct | DefKind::Constant | DefKind::Variable) | None => {}
912 }
913
914 let know_ty_well = def
916 .as_ref()
917 .and_then(|d| self.simplified_type_of_span(d.decl.span()))
918 .filter(|ty| !matches!(ty, Ty::Any))
919 .is_some();
920 if know_ty_well {
921 return def;
922 }
923
924 let def_ref = def.as_ref();
925 let def_name = || Some(def_ref?.name().clone());
926 let dyn_def = self
927 .analyze_expr(syntax.node())
928 .iter()
929 .find_map(|(value, _)| {
930 let def = Definition::from_value(value.clone(), def_name)?;
931 None.or_else(|| {
932 let source = self.source_by_id(def.decl.file_id()?).ok()?;
933 let node = LinkedNode::new(source.root()).find(def.decl.span())?;
934 let def_at_the_span = classify_def_loosely(node)?;
935 self.def_of_span(&source, doc, def_at_the_span.name()?.span())
936 })
937 .or(Some(def))
938 });
939
940 dyn_def.or(def)
942 }
943
944 pub(crate) fn simplified_type_of_span(self: &Arc<Self>, span: Span) -> Option<Ty> {
945 let source = self.source_by_id(span.id()?).ok()?;
946 let (ti, ty) = self.type_of_span_(&source, span)?;
947 Some(ti.simplify(ty, false))
948 }
949
950 pub(crate) fn type_of_span(self: &Arc<Self>, span: Span) -> Option<Ty> {
951 let source = self.source_by_id(span.id()?).ok()?;
952 Some(self.type_of_span_(&source, span)?.1)
953 }
954
955 pub(crate) fn type_of_span_(
956 self: &Arc<Self>,
957 source: &Source,
958 span: Span,
959 ) -> Option<(Arc<TypeInfo>, Ty)> {
960 let ti = self.type_check(source);
961 let ty = ti.type_of_span(span)?;
962 Some((ti, ty))
963 }
964
965 pub(crate) fn post_type_of_node(self: &Arc<Self>, node: LinkedNode) -> Option<Ty> {
966 let id = node.span().id()?;
967 let source = self.source_by_id(id).ok()?;
968 let ty_chk = self.type_check(&source);
969
970 let ty = post_type_check(self.clone(), &ty_chk, node.clone())
971 .or_else(|| ty_chk.type_of_span(node.span()))?;
972 Some(ty_chk.simplify(ty, false))
973 }
974
975 pub(crate) fn sig_of_def(self: &Arc<Self>, def: Definition) -> Option<Signature> {
976 crate::log_debug_ct!("check definition func {def:?}");
977 let source = def.decl.file_id().and_then(|id| self.source_by_id(id).ok());
978 analyze_signature(self, SignatureTarget::Def(source, def))
979 }
980
981 pub(crate) fn sig_of_type(self: &Arc<Self>, ti: &TypeInfo, ty: Ty) -> Option<Signature> {
982 super::sig_of_type(self, ti, ty)
983 }
984
985 pub(crate) fn sig_of_type_or_dyn(
986 self: &Arc<Self>,
987 ti: &TypeInfo,
988 callee_ty: Ty,
989 callee: &SyntaxNode,
990 ) -> Option<Signature> {
991 self.sig_of_type(ti, callee_ty).or_else(|| {
992 self.analyze_expr(callee).iter().find_map(|(value, _)| {
993 let Value::Func(callee) = value else {
994 return None;
995 };
996
997 analyze_signature(self, SignatureTarget::Runtime(callee.clone()))
999 })
1000 })
1001 }
1002
1003 pub fn analyze_import(&self, source: &SyntaxNode) -> (Option<Value>, Option<Value>) {
1010 if let Some(v) = source.cast::<ast::Expr>().and_then(Self::const_eval) {
1011 return (Some(v), None);
1012 }
1013 let token = &self.analysis.workers.import;
1014 token.enter(|| analyze_import_(&self.world, source))
1015 }
1016
1017 pub fn analyze_expr(&self, source: &SyntaxNode) -> EcoVec<(Value, Option<Styles>)> {
1019 let token = &self.analysis.workers.expression;
1020 token.enter(|| analyze_expr_(&self.world, source))
1021 }
1022
1023 pub fn analyze_bib(&self, introspector: &Introspector) -> Option<Arc<BibInfo>> {
1025 let world = &self.world;
1026 let world = (world as &dyn World).track();
1027
1028 analyze_bib(world, introspector.track())
1029 }
1030
1031 pub fn tooltip(&self, source: &Source, cursor: usize) -> Option<Tooltip> {
1037 let token = &self.analysis.workers.tooltip;
1038 token.enter(|| tooltip_(&self.world, source, cursor))
1039 }
1040
1041 pub fn get_manifest(&self, toml_id: TypstFileId) -> StrResult<PackageManifest> {
1043 crate::package::get_manifest(&self.world, toml_id)
1044 }
1045
1046 pub fn compute_signature(
1048 self: &Arc<Self>,
1049 func: SignatureTarget,
1050 compute: impl FnOnce(&Arc<Self>) -> Option<Signature> + Send + Sync + 'static,
1051 ) -> Option<Signature> {
1052 let res = match func {
1053 SignatureTarget::Def(src, def) => self
1054 .analysis
1055 .caches
1056 .def_signatures
1057 .entry(hash128(&(src, def.clone())), self.lifetime),
1058 SignatureTarget::SyntaxFast(source, span) => {
1059 let cache_key = (source, span, true);
1060 self.analysis
1061 .caches
1062 .static_signatures
1063 .entry(hash128(&cache_key), self.lifetime)
1064 }
1065 SignatureTarget::Syntax(source, span) => {
1066 let cache_key = (source, span);
1067 self.analysis
1068 .caches
1069 .static_signatures
1070 .entry(hash128(&cache_key), self.lifetime)
1071 }
1072 SignatureTarget::Convert(rt) => self
1073 .analysis
1074 .caches
1075 .signatures
1076 .entry(hash128(&(&rt, true)), self.lifetime),
1077 SignatureTarget::Runtime(rt) => self
1078 .analysis
1079 .caches
1080 .signatures
1081 .entry(hash128(&rt), self.lifetime),
1082 };
1083 res.get_or_init(|| compute(self)).clone()
1084 }
1085
1086 pub(crate) fn compute_docstring(
1087 self: &Arc<Self>,
1088 fid: TypstFileId,
1089 docs: String,
1090 kind: DefKind,
1091 ) -> Option<Arc<DocString>> {
1092 let res = self
1093 .analysis
1094 .caches
1095 .docstrings
1096 .entry(hash128(&(fid, &docs, kind)), self.lifetime);
1097 res.get_or_init(|| {
1098 crate::syntax::docs::do_compute_docstring(self, fid, docs, kind).map(Arc::new)
1099 })
1100 .clone()
1101 }
1102
1103 pub fn remove_html(&self, markup: EcoString) -> EcoString {
1105 if !self.analysis.remove_html {
1106 return markup;
1107 }
1108
1109 static REMOVE_HTML_COMMENT_REGEX: LazyLock<regex::Regex> =
1110 LazyLock::new(|| regex::Regex::new(r#"<!--[\s\S]*?-->"#).unwrap());
1111 REMOVE_HTML_COMMENT_REGEX
1112 .replace_all(&markup, "")
1113 .trim()
1114 .into()
1115 }
1116
1117 fn query_stat(&self, id: TypstFileId, query: &'static str) -> QueryStatGuard {
1118 let stats = &self.analysis.stats.query_stats;
1119 let entry = stats.entry(id).or_default();
1120 let entry = entry.entry(query).or_default();
1121 QueryStatGuard {
1122 bucket: entry.clone(),
1123 since: tinymist_std::time::Instant::now(),
1124 }
1125 }
1126
1127 pub(crate) fn prefetch_type_check(self: &Arc<Self>, _fid: TypstFileId) {
1130 }
1140
1141 pub(crate) fn preload_package(self: Arc<Self>, entry_point: TypstFileId) {
1142 crate::log_debug_ct!("preload package start {entry_point:?}");
1143
1144 #[derive(Clone)]
1145 struct Preloader {
1146 shared: Arc<SharedContext>,
1147 analyzed: Arc<Mutex<HashSet<TypstFileId>>>,
1148 }
1149
1150 impl Preloader {
1151 fn work(&self, fid: TypstFileId) {
1152 crate::log_debug_ct!("preload package {fid:?}");
1153 let source = self.shared.source_by_id(fid).ok().unwrap();
1154 let exprs = self.shared.expr_stage(&source);
1155 self.shared.type_check(&source);
1156 exprs.imports.iter().for_each(|(fid, _)| {
1157 if !self.analyzed.lock().insert(*fid) {
1158 return;
1159 }
1160 self.work(*fid);
1161 })
1162 }
1163 }
1164
1165 let preloader = Preloader {
1166 shared: self,
1167 analyzed: Arc::default(),
1168 };
1169
1170 preloader.work(entry_point);
1171 }
1172}
1173
1174type DeferredCompute<T> = Arc<OnceLock<T>>;
1176
1177#[derive(Clone)]
1178struct IncrCacheMap<K, V> {
1179 revision: usize,
1180 global: Arc<Mutex<FxDashMap<K, (usize, V)>>>,
1181 prev: Arc<Mutex<FxHashMap<K, DeferredCompute<V>>>>,
1182 next: Arc<Mutex<FxHashMap<K, DeferredCompute<V>>>>,
1183}
1184
1185impl<K: Eq + Hash, V> Default for IncrCacheMap<K, V> {
1186 fn default() -> Self {
1187 Self {
1188 revision: 0,
1189 global: Arc::default(),
1190 prev: Arc::default(),
1191 next: Arc::default(),
1192 }
1193 }
1194}
1195
1196impl<K, V> IncrCacheMap<K, V> {
1197 fn compute(&self, key: K, compute: impl FnOnce(Option<V>) -> V) -> V
1198 where
1199 K: Clone + Eq + Hash,
1200 V: Clone,
1201 {
1202 let next = self.next.lock().entry(key.clone()).or_default().clone();
1203
1204 next.get_or_init(|| {
1205 let prev = self.prev.lock().get(&key).cloned();
1206 let prev = prev.and_then(|prev| prev.get().cloned());
1207 let prev = prev.or_else(|| {
1208 let global = self.global.lock();
1209 global.get(&key).map(|global| global.1.clone())
1210 });
1211
1212 let res = compute(prev);
1213
1214 let global = self.global.lock();
1215 let entry = global.entry(key.clone());
1216 use dashmap::mapref::entry::Entry;
1217 match entry {
1218 Entry::Occupied(mut entry) => {
1219 let (revision, _) = entry.get();
1220 if *revision < self.revision {
1221 entry.insert((self.revision, res.clone()));
1222 }
1223 }
1224 Entry::Vacant(entry) => {
1225 entry.insert((self.revision, res.clone()));
1226 }
1227 }
1228
1229 res
1230 })
1231 .clone()
1232 }
1233
1234 fn crawl(&self, revision: usize) -> Self {
1235 Self {
1236 revision,
1237 prev: self.next.clone(),
1238 global: self.global.clone(),
1239 next: Default::default(),
1240 }
1241 }
1242}
1243
1244#[derive(Clone)]
1245struct CacheMap<T> {
1246 m: Arc<FxDashMap<u128, (u64, T)>>,
1247 }
1249
1250impl<T> Default for CacheMap<T> {
1251 fn default() -> Self {
1252 Self {
1253 m: Default::default(),
1254 }
1256 }
1257}
1258
1259impl<T> CacheMap<T> {
1260 fn clear(&self) {
1261 self.m.clear();
1262 }
1263
1264 fn retain(&self, mut f: impl FnMut(&mut (u64, T)) -> bool) {
1265 self.m.retain(|_k, v| f(v));
1266 }
1267}
1268
1269impl<T: Default + Clone> CacheMap<T> {
1270 fn entry(&self, key: u128, lifetime: u64) -> T {
1271 let entry = self.m.entry(key);
1272 let entry = entry.or_insert_with(|| (lifetime, T::default()));
1273 entry.1.clone()
1274 }
1275}
1276
1277#[derive(Default)]
1279pub struct AnalysisGlobalWorkers {
1280 import: RateLimiter,
1282 expression: RateLimiter,
1284 tooltip: RateLimiter,
1286}
1287
1288#[derive(Default, Clone)]
1291pub struct AnalysisGlobalCaches {
1292 lifetime: Arc<AtomicU64>,
1293 clear_lifetime: Arc<AtomicU64>,
1294 def_signatures: CacheMap<DeferredCompute<Option<Signature>>>,
1295 static_signatures: CacheMap<DeferredCompute<Option<Signature>>>,
1296 signatures: CacheMap<DeferredCompute<Option<Signature>>>,
1297 docstrings: CacheMap<DeferredCompute<Option<Arc<DocString>>>>,
1298 terms: CacheMap<(Value, Ty)>,
1299}
1300
1301#[derive(Default)]
1307pub struct AnalysisLocalCaches {
1308 modules: HashMap<TypstFileId, ModuleAnalysisLocalCache>,
1309 completion_files: OnceLock<Vec<TypstFileId>>,
1310 root_files: OnceLock<Vec<TypstFileId>>,
1311 module_deps: OnceLock<HashMap<TypstFileId, ModuleDependency>>,
1312}
1313
1314#[derive(Default)]
1319pub struct ModuleAnalysisLocalCache {
1320 expr_stage: OnceLock<ExprInfo>,
1321 type_check: OnceLock<Arc<TypeInfo>>,
1322}
1323
1324#[derive(Default)]
1327pub struct AnalysisRevCache {
1328 default_slot: AnalysisRevSlot,
1329 manager: RevisionManager<AnalysisRevSlot>,
1330}
1331
1332impl RevisionManagerLike for AnalysisRevCache {
1333 fn gc(&mut self, rev: usize) {
1334 self.manager.gc(rev);
1335
1336 {
1338 let mut max_ei = FxHashMap::default();
1339 let es = self.default_slot.expr_stage.global.lock();
1340 for r in es.iter() {
1341 let rev: &mut usize = max_ei.entry(r.1.fid).or_default();
1342 *rev = (*rev).max(r.1.revision);
1343 }
1344 es.retain(|_, r| r.1.revision == *max_ei.get(&r.1.fid).unwrap_or(&0));
1345 }
1346
1347 {
1348 let mut max_ti = FxHashMap::default();
1349 let ts = self.default_slot.type_check.global.lock();
1350 for r in ts.iter() {
1351 let rev: &mut usize = max_ti.entry(r.1.fid).or_default();
1352 *rev = (*rev).max(r.1.revision);
1353 }
1354 ts.retain(|_, r| r.1.revision == *max_ti.get(&r.1.fid).unwrap_or(&0));
1355 }
1356
1357 {
1358 let mut max_li = FxHashMap::default();
1359 let ts = self.default_slot.lint.global.lock();
1360 for r in ts.iter() {
1361 let rev: &mut usize = max_li.entry(r.1.fid).or_default();
1362 *rev = (*rev).max(r.1.revision);
1363 }
1364 ts.retain(|_, r| r.1.revision == *max_li.get(&r.1.fid).unwrap_or(&0));
1365 }
1366 }
1367}
1368
1369impl AnalysisRevCache {
1370 fn clear(&mut self) {
1371 self.manager.clear();
1372 self.default_slot = Default::default();
1373 }
1374
1375 fn find_revision(
1377 &mut self,
1378 revision: NonZeroUsize,
1379 lg: &AnalysisRevLock,
1380 ) -> Arc<RevisionSlot<AnalysisRevSlot>> {
1381 lg.inner.access(revision);
1382 self.manager.find_revision(revision, |slot_base| {
1383 log::debug!("analysis revision {} is created", revision.get());
1384 slot_base
1385 .map(|slot| AnalysisRevSlot {
1386 revision: slot.revision,
1387 expr_stage: slot.data.expr_stage.crawl(revision.get()),
1388 type_check: slot.data.type_check.crawl(revision.get()),
1389 lint: slot.data.lint.crawl(revision.get()),
1390 })
1391 .unwrap_or_else(|| self.default_slot.clone())
1392 })
1393 }
1394}
1395
1396pub struct AnalysisRevLock {
1398 inner: RevisionLock,
1399 tokens: Option<SemanticTokenContext>,
1400 grid: Arc<Mutex<AnalysisRevCache>>,
1401}
1402
1403impl Drop for AnalysisRevLock {
1404 fn drop(&mut self) {
1405 let mut mu = self.grid.lock();
1406 let gc_revision = mu.manager.unlock(&mut self.inner);
1407
1408 if let Some(gc_revision) = gc_revision {
1409 let grid = self.grid.clone();
1410 rayon::spawn(move || {
1411 grid.lock().gc(gc_revision);
1412 });
1413 }
1414 }
1415}
1416
1417#[derive(Default, Clone)]
1418struct AnalysisRevSlot {
1419 revision: usize,
1420 expr_stage: IncrCacheMap<u128, ExprInfo>,
1421 type_check: IncrCacheMap<u128, Arc<TypeInfo>>,
1422 lint: IncrCacheMap<u128, LintInfo>,
1423}
1424
1425impl Drop for AnalysisRevSlot {
1426 fn drop(&mut self) {
1427 log::debug!("analysis revision {} is dropped", self.revision);
1428 }
1429}
1430
1431fn ceil_char_boundary(text: &str, mut cursor: usize) -> usize {
1432 while cursor < text.len() && !text.is_char_boundary(cursor) {
1434 cursor += 1;
1435 }
1436
1437 cursor.min(text.len())
1438}
1439
1440#[typst_macros::time]
1441#[comemo::memoize]
1442fn analyze_bib(
1443 world: Tracked<dyn World + '_>,
1444 introspector: Tracked<Introspector>,
1445) -> Option<Arc<BibInfo>> {
1446 let bib_elem = BibliographyElem::find(introspector).ok()?;
1447
1448 let csl_style = bib_elem.style(StyleChain::default()).derived;
1451
1452 let Value::Array(paths) = bib_elem.sources.clone().into_value() else {
1453 return None;
1454 };
1455 let elem_fid = bib_elem.span().id()?;
1456 let files = paths
1457 .into_iter()
1458 .flat_map(|path| path.cast().ok())
1459 .flat_map(|bib_path: EcoString| {
1460 let bib_fid = resolve_id_by_path(world.deref(), elem_fid, &bib_path)?;
1461 Some((bib_fid, world.file(bib_fid).ok()?))
1462 });
1463
1464 bib_info(csl_style, files)
1465}
1466
1467#[comemo::memoize]
1468fn loc_info(bytes: Bytes) -> Option<EcoVec<(usize, String)>> {
1469 let mut loc = EcoVec::new();
1470 let mut offset = 0;
1471 for line in bytes.split(|byte| *byte == b'\n') {
1472 loc.push((offset, String::from_utf8(line.to_owned()).ok()?));
1473 offset += line.len() + 1;
1474 }
1475 Some(loc)
1476}
1477
1478fn find_loc(
1479 len: usize,
1480 loc: &EcoVec<(usize, String)>,
1481 mut offset: usize,
1482 encoding: PositionEncoding,
1483) -> Option<LspPosition> {
1484 if offset > len {
1485 offset = len;
1486 }
1487
1488 let r = match loc.binary_search_by_key(&offset, |line| line.0) {
1489 Ok(i) => i,
1490 Err(i) => i - 1,
1491 };
1492
1493 let (start, s) = loc.get(r)?;
1494 let byte_offset = offset.saturating_sub(*start);
1495
1496 let column_prefix = if byte_offset <= s.len() {
1497 &s[..byte_offset]
1498 } else {
1499 let line = (r + 1) as u32;
1500 return Some(LspPosition { line, character: 0 });
1501 };
1502
1503 let line = r as u32;
1504 let character = match encoding {
1505 PositionEncoding::Utf8 => column_prefix.chars().count(),
1506 PositionEncoding::Utf16 => column_prefix.chars().map(|ch| ch.len_utf16()).sum(),
1507 } as u32;
1508
1509 Some(LspPosition { line, character })
1510}
1511
1512pub struct SearchCtx<'a> {
1514 pub ctx: &'a mut LocalContext,
1516 pub searched: HashSet<TypstFileId>,
1518 pub worklist: Vec<TypstFileId>,
1520}
1521
1522impl SearchCtx<'_> {
1523 pub fn push(&mut self, fid: TypstFileId) -> bool {
1525 if self.searched.insert(fid) {
1526 self.worklist.push(fid);
1527 true
1528 } else {
1529 false
1530 }
1531 }
1532
1533 pub fn push_dependents(&mut self, fid: TypstFileId) {
1535 let deps = self.ctx.module_dependencies().get(&fid);
1536 let dependents = deps.map(|dep| dep.dependents.clone()).into_iter().flatten();
1537 for dep in dependents {
1538 self.push(dep);
1539 }
1540 }
1541}
1542
1543#[derive(Default)]
1545pub struct RateLimiter {
1546 token: std::sync::Mutex<()>,
1547}
1548
1549impl RateLimiter {
1550 #[must_use]
1552 pub fn enter<T>(&self, f: impl FnOnce() -> T) -> T {
1553 let _c = self.token.lock().unwrap();
1554 f()
1555 }
1556}