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