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