1mod bib;
4
5pub(crate) use bib::*;
6pub mod call;
7pub use call::*;
8pub mod completion;
9pub use completion::*;
10pub mod code_action;
11pub use code_action::*;
12pub mod color_expr;
13pub use color_expr::*;
14pub mod doc_highlight;
15pub use doc_highlight::*;
16pub mod link_expr;
17pub use link_expr::*;
18pub mod stats;
19pub use stats::*;
20pub mod definition;
21pub use definition::*;
22pub mod signature;
23pub use signature::*;
24pub mod semantic_tokens;
25pub use semantic_tokens::*;
26mod post_tyck;
27mod tyck;
28pub(crate) use crate::ty::*;
29pub(crate) use post_tyck::*;
30pub(crate) use tyck::*;
31mod prelude;
32
33mod global;
34pub use global::*;
35
36use std::sync::Arc;
37
38use ecow::eco_format;
39use lsp_types::Url;
40use tinymist_project::LspComputeGraph;
41use tinymist_std::{Result, bail};
42use tinymist_world::{EntryReader, TaskInputs};
43use typst::diag::{FileError, FileResult};
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
136#[cfg(test)]
137mod matcher_tests {
138
139 use typst::syntax::LinkedNode;
140 use typst_shim::syntax::LinkedNodeExt;
141
142 use crate::{syntax::classify_def, tests::*};
143
144 #[test]
145 fn test() {
146 snapshot_testing("match_def", &|ctx, path| {
147 let source = ctx.source_by_path(&path).unwrap();
148
149 let pos = ctx
150 .to_typst_pos(find_test_position(&source), &source)
151 .unwrap();
152
153 let root = LinkedNode::new(source.root());
154 let node = root.leaf_at_compat(pos).unwrap();
155
156 let snap = classify_def(node).map(|def| format!("{:?}", def.node().range()));
157 let snap = snap.as_deref().unwrap_or("<nil>");
158
159 assert_snapshot!(snap);
160 });
161 }
162}
163
164#[cfg(test)]
165mod expr_tests {
166
167 use tinymist_std::path::unix_slash;
168 use tinymist_world::vfs::WorkspaceResolver;
169 use typst::syntax::Source;
170
171 use crate::syntax::{Expr, RefExpr};
172 use crate::tests::*;
173
174 trait ShowExpr {
175 fn show_expr(&self, expr: &Expr) -> String;
176 }
177
178 impl ShowExpr for Source {
179 fn show_expr(&self, node: &Expr) -> String {
180 match node {
181 Expr::Decl(decl) => {
182 let range = self.range(decl.span()).unwrap_or_default();
183 let fid = if let Some(fid) = decl.file_id() {
184 let vpath = fid.vpath().as_rooted_path();
185 match fid.package() {
186 Some(package) if WorkspaceResolver::is_package_file(fid) => {
187 format!(" in {package:?}{}", unix_slash(vpath))
188 }
189 Some(_) | None => format!(" in {}", unix_slash(vpath)),
190 }
191 } else {
192 "".to_string()
193 };
194 format!("{decl:?}@{range:?}{fid}")
195 }
196 _ => format!("{node}"),
197 }
198 }
199 }
200
201 #[test]
202 fn docs() {
203 snapshot_testing("docs", &|ctx, path| {
204 let source = ctx.source_by_path(&path).unwrap();
205
206 let result = ctx.shared_().expr_stage(&source);
207 let mut docstrings = result.docstrings.iter().collect::<Vec<_>>();
208 docstrings.sort_by(|x, y| x.0.cmp(y.0));
209 let mut docstrings = docstrings
210 .into_iter()
211 .map(|(ident, expr)| {
212 format!(
213 "{} -> {expr:?}",
214 source.show_expr(&Expr::Decl(ident.clone())),
215 )
216 })
217 .collect::<Vec<_>>();
218 let mut snap = vec![];
219 snap.push("= docstings".to_owned());
220 snap.append(&mut docstrings);
221
222 assert_snapshot!(snap.join("\n"));
223 });
224 }
225
226 #[test]
227 fn scope() {
228 snapshot_testing("expr_of", &|ctx, path| {
229 let source = ctx.source_by_path(&path).unwrap();
230
231 let result = ctx.shared_().expr_stage(&source);
232 let mut resolves = result.resolves.iter().collect::<Vec<_>>();
233 resolves.sort_by(|x, y| x.1.decl.cmp(&y.1.decl));
234
235 let mut resolves = resolves
236 .into_iter()
237 .map(|(_, expr)| {
238 let RefExpr {
239 decl: ident,
240 step,
241 root,
242 term,
243 } = expr.as_ref();
244
245 format!(
246 "{} -> {}, root {}, val: {term:?}",
247 source.show_expr(&Expr::Decl(ident.clone())),
248 step.as_ref()
249 .map(|expr| source.show_expr(expr))
250 .unwrap_or_default(),
251 root.as_ref()
252 .map(|expr| source.show_expr(expr))
253 .unwrap_or_default()
254 )
255 })
256 .collect::<Vec<_>>();
257 let mut exports = result.exports.iter().collect::<Vec<_>>();
258 exports.sort_by(|x, y| x.0.cmp(y.0));
259 let mut exports = exports
260 .into_iter()
261 .map(|(ident, node)| {
262 let node = source.show_expr(node);
263 format!("{ident} -> {node}",)
264 })
265 .collect::<Vec<_>>();
266
267 let mut snap = vec![];
268 snap.push("= resolves".to_owned());
269 snap.append(&mut resolves);
270 snap.push("= exports".to_owned());
271 snap.append(&mut exports);
272
273 assert_snapshot!(snap.join("\n"));
274 });
275 }
276}
277
278#[cfg(test)]
279mod module_tests {
280 use serde_json::json;
281 use tinymist_std::path::unix_slash;
282 use typst::syntax::FileId;
283
284 use crate::prelude::*;
285 use crate::syntax::module::*;
286 use crate::tests::*;
287
288 #[test]
289 fn test() {
290 snapshot_testing("modules", &|ctx, _| {
291 fn ids(ids: EcoVec<FileId>) -> Vec<String> {
292 let mut ids: Vec<String> = ids
293 .into_iter()
294 .map(|id| unix_slash(id.vpath().as_rooted_path()))
295 .collect();
296 ids.sort();
297 ids
298 }
299
300 let dependencies = construct_module_dependencies(ctx);
301
302 let mut dependencies = dependencies
303 .into_iter()
304 .map(|(id, v)| {
305 (
306 unix_slash(id.vpath().as_rooted_path()),
307 ids(v.dependencies),
308 ids(v.dependents),
309 )
310 })
311 .collect::<Vec<_>>();
312
313 dependencies.sort();
314 dependencies.retain(|(path, _, _)| path != "/main.typ");
316
317 let dependencies = dependencies
318 .into_iter()
319 .map(|(id, deps, dependents)| {
320 let mut mp = serde_json::Map::new();
321 mp.insert("id".to_string(), json!(id));
322 mp.insert("dependencies".to_string(), json!(deps));
323 mp.insert("dependents".to_string(), json!(dependents));
324 json!(mp)
325 })
326 .collect::<Vec<_>>();
327
328 assert_snapshot!(JsonRepr::new_pure(dependencies));
329 });
330 }
331}
332
333#[cfg(test)]
334mod type_check_tests {
335
336 use core::fmt;
337
338 use typst::syntax::Source;
339
340 use crate::tests::*;
341
342 use super::{Ty, TypeInfo};
343
344 #[test]
345 fn test() {
346 snapshot_testing("type_check", &|ctx, path| {
347 let source = ctx.source_by_path(&path).unwrap();
348
349 let result = ctx.type_check(&source);
350 let result = format!("{:#?}", TypeCheckSnapshot(&source, &result));
351
352 assert_snapshot!(result);
353 });
354 }
355
356 struct TypeCheckSnapshot<'a>(&'a Source, &'a TypeInfo);
357
358 impl fmt::Debug for TypeCheckSnapshot<'_> {
359 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
360 let source = self.0;
361 let info = self.1;
362 let mut vars = info
363 .vars
364 .values()
365 .map(|bounds| (bounds.name(), bounds))
366 .collect::<Vec<_>>();
367
368 vars.sort_by(|x, y| x.1.var.strict_cmp(&y.1.var));
369
370 for (name, bounds) in vars {
371 writeln!(f, "{name:?} = {:?}", info.simplify(bounds.as_type(), true))?;
372 }
373
374 writeln!(f, "=====")?;
375 let mut mapping = info
376 .mapping
377 .iter()
378 .map(|pair| (source.range(*pair.0).unwrap_or_default(), pair.1))
379 .collect::<Vec<_>>();
380
381 mapping.sort_by(|x, y| {
382 x.0.start
383 .cmp(&y.0.start)
384 .then_with(|| x.0.end.cmp(&y.0.end))
385 });
386
387 for (range, value) in mapping {
388 let ty = Ty::from_types(value.clone().into_iter());
389 writeln!(f, "{range:?} -> {ty:?}")?;
390 }
391
392 Ok(())
393 }
394 }
395}
396
397#[cfg(test)]
398mod post_type_check_tests {
399
400 use typst::syntax::LinkedNode;
401 use typst_shim::syntax::LinkedNodeExt;
402
403 use crate::analysis::*;
404 use crate::tests::*;
405
406 #[test]
407 fn test() {
408 snapshot_testing("post_type_check", &|ctx, path| {
409 let source = ctx.source_by_path(&path).unwrap();
410
411 let pos = ctx
412 .to_typst_pos(find_test_position(&source), &source)
413 .unwrap();
414 let root = LinkedNode::new(source.root());
415 let node = root.leaf_at_compat(pos + 1).unwrap();
416 let text = node.get().clone().into_text();
417
418 let result = ctx.type_check(&source);
419 let post_ty = post_type_check(ctx.shared_(), &result, node);
420
421 with_settings!({
422 description => format!("Check on {text:?} ({pos:?})"),
423 }, {
424 let post_ty = post_ty.map(|ty| format!("{ty:#?}"))
425 .unwrap_or_else(|| "<nil>".to_string());
426 assert_snapshot!(post_ty);
427 })
428 });
429 }
430}
431
432#[cfg(test)]
433mod type_describe_tests {
434
435 use typst::syntax::LinkedNode;
436 use typst_shim::syntax::LinkedNodeExt;
437
438 use crate::analysis::*;
439 use crate::tests::*;
440
441 #[test]
442 fn test() {
443 snapshot_testing("type_describe", &|ctx, path| {
444 let source = ctx.source_by_path(&path).unwrap();
445
446 let pos = ctx
447 .to_typst_pos(find_test_position(&source), &source)
448 .unwrap();
449 let root = LinkedNode::new(source.root());
450 let node = root.leaf_at_compat(pos + 1).unwrap();
451 let text = node.get().clone().into_text();
452
453 let ti = ctx.type_check(&source);
454 let post_ty = post_type_check(ctx.shared_(), &ti, node);
455
456 with_settings!({
457 description => format!("Check on {text:?} ({pos:?})"),
458 }, {
459 let post_ty = post_ty.and_then(|ty| ty.describe())
460 .unwrap_or_else(|| "<nil>".into());
461 assert_snapshot!(post_ty);
462 })
463 });
464 }
465}
466
467#[cfg(test)]
468mod signature_tests {
469
470 use core::fmt;
471
472 use typst::syntax::LinkedNode;
473 use typst_shim::syntax::LinkedNodeExt;
474
475 use crate::analysis::{Signature, SignatureTarget, analyze_signature};
476 use crate::syntax::classify_syntax;
477 use crate::tests::*;
478
479 #[test]
480 fn test() {
481 snapshot_testing("signature", &|ctx, path| {
482 let source = ctx.source_by_path(&path).unwrap();
483
484 let pos = ctx
485 .to_typst_pos(find_test_position(&source), &source)
486 .unwrap();
487
488 let root = LinkedNode::new(source.root());
489 let callee_node = root.leaf_at_compat(pos).unwrap();
490 let callee_node = classify_syntax(callee_node, pos).unwrap();
491 let callee_node = callee_node.node();
492
493 let result = analyze_signature(
494 ctx.shared(),
495 SignatureTarget::Syntax(source.clone(), callee_node.span()),
496 );
497
498 assert_snapshot!(SignatureSnapshot(result.as_ref()));
499 });
500 }
501
502 struct SignatureSnapshot<'a>(pub Option<&'a Signature>);
503
504 impl fmt::Display for SignatureSnapshot<'_> {
505 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
506 let Some(sig) = self.0 else {
507 return write!(f, "<nil>");
508 };
509
510 let primary_sig = match sig {
511 Signature::Primary(sig) => sig,
512 Signature::Partial(sig) => {
513 for w in &sig.with_stack {
514 write!(f, "with ")?;
515 for arg in &w.items {
516 if let Some(name) = &arg.name {
517 write!(f, "{name}: ")?;
518 }
519 let term = arg.term.as_ref();
520 let term = term.and_then(|v| v.describe()).unwrap_or_default();
521 write!(f, "{term}, ")?;
522 }
523 f.write_str("\n")?;
524 }
525
526 &sig.signature
527 }
528 };
529
530 writeln!(f, "fn(")?;
531 for param in primary_sig.pos() {
532 writeln!(f, " {},", param.name)?;
533 }
534 for param in primary_sig.named() {
535 if let Some(expr) = ¶m.default {
536 writeln!(f, " {}: {},", param.name, expr)?;
537 } else {
538 writeln!(f, " {},", param.name)?;
539 }
540 }
541 if let Some(param) = primary_sig.rest() {
542 writeln!(f, " ...{}, ", param.name)?;
543 }
544 write!(f, ")")?;
545
546 Ok(())
547 }
548 }
549}
550
551#[cfg(test)]
552mod call_info_tests {
553
554 use core::fmt;
555
556 use typst::syntax::{LinkedNode, SyntaxKind};
557 use typst_shim::syntax::LinkedNodeExt;
558
559 use crate::analysis::analyze_call;
560 use crate::tests::*;
561
562 use super::CallInfo;
563
564 #[test]
565 fn test() {
566 snapshot_testing("call_info", &|ctx, path| {
567 let source = ctx.source_by_path(&path).unwrap();
568
569 let pos = ctx
570 .to_typst_pos(find_test_position(&source), &source)
571 .unwrap();
572
573 let root = LinkedNode::new(source.root());
574 let mut call_node = root.leaf_at_compat(pos + 1).unwrap();
575
576 while let Some(parent) = call_node.parent() {
577 if call_node.kind() == SyntaxKind::FuncCall {
578 break;
579 }
580 call_node = parent.clone();
581 }
582
583 let result = analyze_call(ctx, source.clone(), call_node);
584
585 assert_snapshot!(CallSnapshot(result.as_deref()));
586 });
587 }
588
589 struct CallSnapshot<'a>(pub Option<&'a CallInfo>);
590
591 impl fmt::Display for CallSnapshot<'_> {
592 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
593 let Some(ci) = self.0 else {
594 return write!(f, "<nil>");
595 };
596
597 let mut w = ci.arg_mapping.iter().collect::<Vec<_>>();
598 w.sort_by(|x, y| x.0.span().into_raw().cmp(&y.0.span().into_raw()));
599
600 for (arg, arg_call_info) in w {
601 writeln!(f, "{} -> {:?}", arg.clone().into_text(), arg_call_info)?;
602 }
603
604 Ok(())
605 }
606 }
607}
608
609#[cfg(test)]
610mod lint_tests {
611 use std::collections::BTreeMap;
612
613 use crate::tests::*;
614
615 #[test]
616 fn test() {
617 snapshot_testing("lint", &|ctx, path| {
618 let source = ctx.source_by_path(&path).unwrap();
619
620 let result = ctx.lint(&source);
621 let result = crate::diagnostics::DiagWorker::new(ctx).convert_all(result.iter());
622 let result = result
623 .into_iter()
624 .map(|(k, v)| (file_path_(&k), v))
625 .collect::<BTreeMap<_, _>>();
626 assert_snapshot!(JsonRepr::new_redacted(result, &REDACT_LOC));
627 });
628 }
629}