1mod bib;
4pub(crate) use bib::*;
5mod expr_stage;
6use expr_stage::*;
7mod pdg;
8use pdg::*;
9pub mod call;
10pub use call::*;
11pub mod completion;
12pub use completion::*;
13pub mod code_action;
14pub use code_action::*;
15pub mod color_expr;
16pub use color_expr::*;
17pub mod doc_highlight;
18pub use doc_highlight::*;
19pub mod link_expr;
20pub use link_expr::*;
21pub mod definition;
22pub use definition::*;
23pub mod signature;
24pub use signature::*;
25pub mod semantic_tokens;
26pub use semantic_tokens::*;
27
28mod global;
29mod post_tyck;
30mod prelude;
31mod tyck;
32
33pub(crate) use crate::ty::*;
34pub use global::*;
35pub(crate) use post_tyck::*;
36pub(crate) use tinymist_analysis::stats::{AnalysisStats, QueryStatGuard};
37pub(crate) use tyck::*;
38pub use typst_shim::syntax::VirtualPathExt;
39
40use std::sync::Arc;
41
42use ecow::eco_format;
43use lsp_types::Url;
44use tinymist_project::LspComputeGraph;
45use tinymist_std::error::WithContextUntyped;
46use tinymist_std::{Result, bail};
47use tinymist_world::{EntryReader, EntryState, TaskInputs};
48use typst::diag::{FileError, FileResult, StrResult};
49use typst::foundations::{Func, Value};
50use typst::syntax::FileId;
51
52use crate::{CompilerQueryResponse, SemanticRequest, path_res_to_url};
53
54pub(crate) trait ToFunc {
55 fn to_func(&self) -> Option<Func>;
56}
57
58impl ToFunc for Value {
59 fn to_func(&self) -> Option<Func> {
60 match self {
61 Value::Func(func) => Some(func.clone()),
62 Value::Type(ty) => ty.constructor().ok(),
63 _ => None,
64 }
65 }
66}
67
68pub trait LspWorldExt {
70 fn uri_for_id(&self, fid: FileId) -> FileResult<Url>;
72}
73
74impl LspWorldExt for tinymist_project::LspWorld {
75 fn uri_for_id(&self, fid: FileId) -> Result<Url, FileError> {
76 let res = path_res_to_url(self.path_for_id(fid)?);
77
78 crate::log_debug_ct!("uri_for_id: {fid:?} -> {res:?}");
79 res.map_err(|err| FileError::Other(Some(eco_format!("convert to url: {err:?}"))))
80 }
81}
82
83pub struct LspQuerySnapshot {
85 pub snap: LspComputeGraph,
87 analysis: Arc<Analysis>,
89 rev_lock: AnalysisRevLock,
91}
92
93impl std::ops::Deref for LspQuerySnapshot {
94 type Target = LspComputeGraph;
95
96 fn deref(&self) -> &Self::Target {
97 &self.snap
98 }
99}
100
101impl LspQuerySnapshot {
102 pub fn task(mut self, inputs: TaskInputs) -> Self {
104 self.snap = self.snap.task(inputs);
105 self
106 }
107
108 pub fn run_semantic<T: SemanticRequest>(
110 self,
111 query: T,
112 wrapper: fn(Option<T::Response>) -> CompilerQueryResponse,
113 ) -> Result<CompilerQueryResponse> {
114 self.run_analysis(|ctx| query.request(ctx)).map(wrapper)
115 }
116
117 pub fn run_analysis<T>(self, f: impl FnOnce(&mut LocalContextGuard) -> T) -> Result<T> {
119 let graph = self.snap.clone();
120 let Some(..) = graph.world().main_id() else {
121 log::error!("Project: main file is not set");
122 bail!("main file is not set");
123 };
124
125 let mut ctx = self.analysis.enter_(graph, self.rev_lock);
126 Ok(f(&mut ctx))
127 }
128
129 pub fn run_within_package<T>(
131 self,
132 info: &crate::package::PackageInfo,
133 f: impl FnOnce(&mut LocalContextGuard) -> Result<T> + Send + Sync,
134 ) -> Result<T> {
135 let world = self.world();
136
137 let entry: StrResult<EntryState> = Ok(()).and_then(|_| {
138 let toml_id = crate::package::get_manifest_id(info)?;
139 let toml_path = world.path_for_id(toml_id)?.as_path().to_owned();
140 let pkg_root = toml_path
141 .parent()
142 .ok_or_else(|| eco_format!("cannot get package root (parent of {toml_path:?})"))?;
143
144 let manifest = crate::package::get_manifest(world, toml_id)?;
145 let entry_point =
146 crate::package::package_entrypoint_id(toml_id, &manifest.package.entrypoint);
147
148 Ok(EntryState::new_rooted_by_id(pkg_root.into(), entry_point))
149 });
150 let entry = entry.context_ut("resolve package entry")?;
151
152 let snap = self.task(TaskInputs {
153 entry: Some(entry),
154 inputs: None,
155 });
156
157 snap.run_analysis(f)?
158 }
159}
160
161#[cfg(test)]
162mod matcher_tests {
163
164 use typst::syntax::LinkedNode;
165 use typst_shim::syntax::LinkedNodeExt;
166
167 use crate::{syntax::classify_def, tests::*};
168
169 #[test]
170 fn test() {
171 snapshot_testing("match_def", &|ctx, path| {
172 let source = ctx.source_by_path(&path).unwrap();
173
174 let pos = ctx
175 .to_typst_pos(find_test_position(&source), &source)
176 .unwrap();
177
178 let root = LinkedNode::new(source.root());
179 let node = root.leaf_at_compat(pos).unwrap();
180
181 let snap = classify_def(node).map(|def| format!("{:?}", def.node().range()));
182 let snap = snap.as_deref().unwrap_or("<nil>");
183
184 assert_snapshot!(snap);
185 });
186 }
187}
188
189#[cfg(test)]
190mod expr_tests {
191
192 use tinymist_std::path::unix_slash;
193 use tinymist_world::vfs::WorkspaceResolver;
194 use typst::syntax::Source;
195 use typst_shim::syntax::{RootedPathExt, VirtualPathExt, source_range};
196
197 use crate::syntax::{Expr, RefExpr};
198 use crate::tests::*;
199
200 trait ShowExpr {
201 fn show_expr(&self, expr: &Expr) -> String;
202 }
203
204 impl ShowExpr for Source {
205 fn show_expr(&self, node: &Expr) -> String {
206 match node {
207 Expr::Decl(decl) => {
208 let range = source_range(self, decl.span()).unwrap_or_default();
209 let fid = if let Some(fid) = decl.file_id() {
210 if WorkspaceResolver::is_package_file(fid) {
211 let package = fid.package_compat().expect("package file");
212 format!(
213 " in {package:?}{}",
214 unix_slash(fid.vpath().as_rooted_path_compat())
215 )
216 } else {
217 format!(" in {}", unix_slash(fid.vpath().as_rooted_path_compat()))
218 }
219 } else {
220 "".to_string()
221 };
222 format!("{decl:?}@{range:?}{fid}")
223 }
224 _ => format!("{node}"),
225 }
226 }
227 }
228
229 #[test]
230 fn docs() {
231 snapshot_testing("docs", &|ctx, path| {
232 let source = ctx.source_by_path(&path).unwrap();
233
234 let result = ctx.shared_().expr_stage(&source);
235 let mut docstrings = result.docstrings.iter().collect::<Vec<_>>();
236 docstrings.sort_by(|x, y| x.0.cmp(y.0));
237 let mut docstrings = docstrings
238 .into_iter()
239 .map(|(ident, expr)| {
240 format!(
241 "{} -> {expr:?}",
242 source.show_expr(&Expr::Decl(ident.clone())),
243 )
244 })
245 .collect::<Vec<_>>();
246 let mut snap = vec![];
247 snap.push("= docstings".to_owned());
248 snap.append(&mut docstrings);
249
250 assert_snapshot!(snap.join("\n"));
251 });
252 }
253
254 #[test]
255 fn scope() {
256 snapshot_testing("expr_of", &|ctx, path| {
257 let source = ctx.source_by_path(&path).unwrap();
258
259 let result = ctx.shared_().expr_stage(&source);
260 let mut resolves = result.resolves.iter().collect::<Vec<_>>();
261 resolves.sort_by(|x, y| x.1.decl.cmp(&y.1.decl));
262
263 let mut resolves = resolves
264 .into_iter()
265 .map(|(_, expr)| {
266 let RefExpr {
267 decl: ident,
268 step,
269 root,
270 term,
271 } = expr.as_ref();
272
273 format!(
274 "{} -> {}, root {}, val: {term:?}",
275 source.show_expr(&Expr::Decl(ident.clone())),
276 step.as_ref()
277 .map(|expr| source.show_expr(expr))
278 .unwrap_or_default(),
279 root.as_ref()
280 .map(|expr| source.show_expr(expr))
281 .unwrap_or_default()
282 )
283 })
284 .collect::<Vec<_>>();
285 let mut exports = result.exports.iter().collect::<Vec<_>>();
286 exports.sort_by(|x, y| x.0.cmp(y.0));
287 let mut exports = exports
288 .into_iter()
289 .map(|(ident, node)| {
290 let node = source.show_expr(node);
291 format!("{ident} -> {node}",)
292 })
293 .collect::<Vec<_>>();
294
295 let mut snap = vec![];
296 snap.push("= resolves".to_owned());
297 snap.append(&mut resolves);
298 snap.push("= exports".to_owned());
299 snap.append(&mut exports);
300
301 assert_snapshot!(snap.join("\n"));
302 });
303 }
304}
305
306#[cfg(test)]
307mod module_tests {
308 use serde_json::json;
309 use tinymist_std::path::unix_slash;
310 use typst::syntax::FileId;
311
312 use crate::prelude::*;
313 use crate::syntax::module::*;
314 use crate::tests::*;
315
316 #[test]
317 fn test() {
318 snapshot_testing("modules", &|ctx, _| {
319 fn ids(ids: EcoVec<FileId>) -> Vec<String> {
320 let mut ids: Vec<String> = ids
321 .into_iter()
322 .map(|id| unix_slash(id.vpath().as_rooted_path_compat()))
323 .collect();
324 ids.sort();
325 ids
326 }
327
328 let dependencies = construct_module_dependencies(ctx);
329
330 let mut dependencies = dependencies
331 .into_iter()
332 .map(|(id, v)| {
333 (
334 unix_slash(id.vpath().as_rooted_path_compat()),
335 ids(v.dependencies),
336 ids(v.dependents),
337 )
338 })
339 .collect::<Vec<_>>();
340
341 dependencies.sort();
342 dependencies.retain(|(path, _, _)| path != "/main.typ");
344
345 let dependencies = dependencies
346 .into_iter()
347 .map(|(id, deps, dependents)| {
348 let mut mp = serde_json::Map::new();
349 mp.insert("id".to_string(), json!(id));
350 mp.insert("dependencies".to_string(), json!(deps));
351 mp.insert("dependents".to_string(), json!(dependents));
352 json!(mp)
353 })
354 .collect::<Vec<_>>();
355
356 assert_snapshot!(JsonRepr::new_pure(dependencies));
357 });
358 }
359}
360
361#[cfg(test)]
362mod type_check_tests {
363
364 use core::fmt;
365
366 use typst::syntax::Source;
367
368 use crate::tests::*;
369 use typst_shim::syntax::source_range;
370
371 use super::{Ty, TypeInfo};
372
373 #[test]
374 fn test() {
375 snapshot_testing("type_check", &|ctx, path| {
376 let source = ctx.source_by_path(&path).unwrap();
377
378 let result = ctx.type_check(&source);
379 let result = format!("{:#?}", TypeCheckSnapshot(&source, &result));
380
381 assert_snapshot!(result);
382 });
383 }
384
385 struct TypeCheckSnapshot<'a>(&'a Source, &'a TypeInfo);
386
387 impl fmt::Debug for TypeCheckSnapshot<'_> {
388 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
389 let source = self.0;
390 let info = self.1;
391 let mut vars = info
392 .vars
393 .values()
394 .map(|bounds| (bounds.name(), bounds))
395 .collect::<Vec<_>>();
396
397 vars.sort_by(|x, y| x.1.var.strict_cmp(&y.1.var));
398
399 for (name, bounds) in vars {
400 writeln!(f, "{name:?} = {:?}", info.simplify(bounds.as_type(), true))?;
401 }
402
403 writeln!(f, "=====")?;
404 let mut mapping = info
405 .mapping
406 .iter()
407 .map(|pair| (source_range(source, *pair.0).unwrap_or_default(), pair.1))
408 .collect::<Vec<_>>();
409
410 mapping.sort_by(|x, y| {
411 x.0.start
412 .cmp(&y.0.start)
413 .then_with(|| x.0.end.cmp(&y.0.end))
414 });
415
416 for (range, value) in mapping {
417 let ty = Ty::from_types(value.clone().into_iter());
418 writeln!(f, "{range:?} -> {ty:?}")?;
419 }
420
421 Ok(())
422 }
423 }
424}
425
426#[cfg(test)]
427mod post_type_check_tests {
428
429 use typst::syntax::LinkedNode;
430 use typst_shim::syntax::LinkedNodeExt;
431
432 use crate::analysis::*;
433 use crate::tests::*;
434
435 #[test]
436 fn test() {
437 snapshot_testing("post_type_check", &|ctx, path| {
438 let source = ctx.source_by_path(&path).unwrap();
439
440 let pos = ctx
441 .to_typst_pos(find_test_position(&source), &source)
442 .unwrap();
443 let root = LinkedNode::new(source.root());
444 let node = root.leaf_at_compat(pos + 1).unwrap();
445 let text = node.get().clone().full_text();
446
447 let result = ctx.type_check(&source);
448 let post_ty = post_type_check(ctx.shared_(), &result, node);
449
450 with_settings!({
451 description => format!("Check on {text:?} ({pos:?})"),
452 }, {
453 let post_ty = post_ty.map(|ty| format!("{ty:#?}"))
454 .unwrap_or_else(|| "<nil>".to_string());
455 assert_snapshot!(post_ty);
456 })
457 });
458 }
459}
460
461#[cfg(test)]
462mod type_describe_tests {
463
464 use typst::syntax::LinkedNode;
465 use typst_shim::syntax::LinkedNodeExt;
466
467 use crate::analysis::*;
468 use crate::tests::*;
469
470 #[test]
471 fn test() {
472 snapshot_testing("type_describe", &|ctx, path| {
473 let source = ctx.source_by_path(&path).unwrap();
474
475 let pos = ctx
476 .to_typst_pos(find_test_position(&source), &source)
477 .unwrap();
478 let root = LinkedNode::new(source.root());
479 let node = root.leaf_at_compat(pos + 1).unwrap();
480 let text = node.get().clone().full_text();
481
482 let ti = ctx.type_check(&source);
483 let post_ty = post_type_check(ctx.shared_(), &ti, node);
484
485 with_settings!({
486 description => format!("Check on {text:?} ({pos:?})"),
487 }, {
488 let post_ty = post_ty.and_then(|ty| ty.describe())
489 .unwrap_or_else(|| "<nil>".into());
490 assert_snapshot!(post_ty);
491 })
492 });
493 }
494}
495
496#[cfg(test)]
497mod signature_tests {
498
499 use core::fmt;
500
501 use typst::syntax::LinkedNode;
502 use typst_shim::syntax::LinkedNodeExt;
503
504 use crate::analysis::{Signature, SignatureTarget, analyze_signature};
505 use crate::syntax::classify_syntax;
506 use crate::tests::*;
507
508 #[test]
509 fn test() {
510 snapshot_testing("signature", &|ctx, path| {
511 let source = ctx.source_by_path(&path).unwrap();
512
513 let pos = ctx
514 .to_typst_pos(find_test_position(&source), &source)
515 .unwrap();
516
517 let root = LinkedNode::new(source.root());
518 let callee_node = root.leaf_at_compat(pos).unwrap();
519 let callee_node = classify_syntax(callee_node, pos).unwrap();
520 let callee_node = callee_node.node();
521
522 let result = analyze_signature(
523 ctx.shared(),
524 SignatureTarget::Syntax(source.clone(), callee_node.span()),
525 );
526
527 assert_snapshot!(SignatureSnapshot(result.as_ref()));
528 });
529 }
530
531 struct SignatureSnapshot<'a>(pub Option<&'a Signature>);
532
533 impl fmt::Display for SignatureSnapshot<'_> {
534 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
535 let Some(sig) = self.0 else {
536 return write!(f, "<nil>");
537 };
538
539 let primary_sig = match sig {
540 Signature::Primary(sig) => sig,
541 Signature::Partial(sig) => {
542 for w in &sig.with_stack {
543 write!(f, "with ")?;
544 for arg in &w.items {
545 if let Some(name) = &arg.name {
546 write!(f, "{name}: ")?;
547 }
548 let term = arg.term.as_ref();
549 let term = term.and_then(|v| v.describe()).unwrap_or_default();
550 write!(f, "{term}, ")?;
551 }
552 f.write_str("\n")?;
553 }
554
555 &sig.signature
556 }
557 };
558
559 writeln!(f, "fn(")?;
560 for param in primary_sig.pos() {
561 writeln!(f, " {},", param.name)?;
562 }
563 for param in primary_sig.named() {
564 if let Some(expr) = ¶m.default {
565 writeln!(f, " {}: {},", param.name, expr)?;
566 } else {
567 writeln!(f, " {},", param.name)?;
568 }
569 }
570 if let Some(param) = primary_sig.rest() {
571 writeln!(f, " ...{}, ", param.name)?;
572 }
573 write!(f, ")")?;
574
575 Ok(())
576 }
577 }
578}
579
580#[cfg(test)]
581mod call_info_tests {
582
583 use core::fmt;
584
585 use typst::syntax::{LinkedNode, SyntaxKind};
586 use typst_shim::syntax::LinkedNodeExt;
587
588 use crate::analysis::analyze_call;
589 use crate::tests::*;
590
591 use super::CallInfo;
592
593 #[test]
594 fn test() {
595 snapshot_testing("call_info", &|ctx, path| {
596 let source = ctx.source_by_path(&path).unwrap();
597
598 let pos = ctx
599 .to_typst_pos(find_test_position(&source), &source)
600 .unwrap();
601
602 let root = LinkedNode::new(source.root());
603 let mut call_node = root.leaf_at_compat(pos + 1).unwrap();
604
605 while let Some(parent) = call_node.parent() {
606 if call_node.kind() == SyntaxKind::FuncCall {
607 break;
608 }
609 call_node = parent.clone();
610 }
611
612 let result = analyze_call(ctx, source.clone(), call_node);
613
614 assert_snapshot!(CallSnapshot(result.as_deref()));
615 });
616 }
617
618 struct CallSnapshot<'a>(pub Option<&'a CallInfo>);
619
620 impl fmt::Display for CallSnapshot<'_> {
621 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
622 let Some(ci) = self.0 else {
623 return write!(f, "<nil>");
624 };
625
626 let mut w = ci.arg_mapping.iter().collect::<Vec<_>>();
627 w.sort_by(|x, y| x.0.span().into_raw().cmp(&y.0.span().into_raw()));
628
629 for (arg, arg_call_info) in w {
630 writeln!(f, "{} -> {:?}", arg.clone().full_text(), arg_call_info)?;
631 }
632
633 Ok(())
634 }
635 }
636}
637
638#[cfg(test)]
639mod lint_tests {
640 use std::collections::BTreeMap;
641
642 use tinymist_lint::KnownIssues;
643
644 use crate::tests::*;
645
646 #[test]
647 fn test() {
648 snapshot_testing("lint", &|ctx, path| {
649 let source = ctx.source_by_path(&path).unwrap();
650
651 let result = ctx.lint(&source, &KnownIssues::default());
652 let result = crate::diagnostics::DiagWorker::new(ctx).convert_all(result.iter());
653 let result = result
654 .into_iter()
655 .map(|(k, v)| (file_uri_(&k), v))
656 .collect::<BTreeMap<_, _>>();
657 assert_snapshot!(JsonRepr::new_redacted(result, &REDACT_LOC));
658 });
659 }
660}