tinymist_debug/
lib.rs

1//! Tinymist coverage support for Typst.
2
3pub use cov::CoverageResult;
4pub use debugger::{
5    BreakpointKind, DebugSession, DebugSessionHandler, set_debug_session, with_debug_session,
6};
7
8mod cov;
9mod debugger;
10mod instrument;
11
12use std::ops::DerefMut;
13use std::sync::Arc;
14
15use debugger::BreakpointInstr;
16use parking_lot::Mutex;
17use tinymist_std::{error::prelude::*, hash::FxHashMap};
18use tinymist_world::package::PackageSpec;
19use tinymist_world::{CompilerFeat, CompilerWorld, print_diagnostics};
20use typst::Library;
21use typst::diag::EcoString;
22use typst::syntax::package::PackageVersion;
23use typst::utils::LazyHash;
24
25use cov::*;
26use instrument::InstrumentWorld;
27
28/// Collects the coverage of a single execution.
29pub fn collect_coverage<D: typst::Document, F: CompilerFeat>(
30    base: &CompilerWorld<F>,
31) -> Result<CoverageResult> {
32    let (cov, result) = with_cov(base, |instr| {
33        if let Err(e) = typst::compile::<D>(&instr).output {
34            print_diagnostics(instr, e.iter(), tinymist_world::DiagnosticFormat::Human)
35                .context_ut("failed to print diagnostics")?;
36            bail!("");
37        }
38
39        Ok(())
40    });
41
42    result?;
43    cov
44}
45
46/// Collects the coverage with a callback.
47pub fn with_cov<F: CompilerFeat, T>(
48    base: &CompilerWorld<F>,
49    mut f: impl FnMut(&InstrumentWorld<F, CovInstr>) -> Result<T>,
50) -> (Result<CoverageResult>, Result<T>) {
51    let instr = InstrumentWorld {
52        base,
53        library: instrument_library(&base.library),
54        instr: CovInstr::default(),
55        instrumented: Mutex::new(FxHashMap::default()),
56    };
57
58    let _cov_lock = cov::COVERAGE_LOCK.lock();
59
60    let result = f(&instr);
61
62    let meta = std::mem::take(instr.instr.map.lock().deref_mut());
63    let CoverageMap { regions, .. } = std::mem::take(cov::COVERAGE_MAP.lock().deref_mut());
64
65    (Ok(CoverageResult { meta, regions }), result)
66}
67
68/// The world for debugging.
69pub type DebuggerWorld<'a, F> = InstrumentWorld<'a, F, BreakpointInstr>;
70/// Creates a world for debugging.
71pub fn instr_breakpoints<F: CompilerFeat>(base: &CompilerWorld<F>) -> DebuggerWorld<'_, F> {
72    InstrumentWorld {
73        base,
74        library: instrument_library(&base.library),
75        instr: BreakpointInstr::default(),
76        instrumented: Mutex::new(FxHashMap::default()),
77    }
78}
79
80#[comemo::memoize]
81fn instrument_library(library: &Arc<LazyHash<Library>>) -> Arc<LazyHash<Library>> {
82    use debugger::breakpoints::*;
83
84    let mut library = library.as_ref().clone();
85
86    let scope = library.global.scope_mut();
87    scope.define_func::<__cov_pc>();
88    scope.define_func::<__breakpoint_call_start>();
89    scope.define_func::<__breakpoint_call_end>();
90    scope.define_func::<__breakpoint_function>();
91    scope.define_func::<__breakpoint_break>();
92    scope.define_func::<__breakpoint_continue>();
93    scope.define_func::<__breakpoint_return>();
94    scope.define_func::<__breakpoint_block_start>();
95    scope.define_func::<__breakpoint_block_end>();
96    scope.define_func::<__breakpoint_show_start>();
97    scope.define_func::<__breakpoint_show_end>();
98    scope.define_func::<__breakpoint_doc_start>();
99    scope.define_func::<__breakpoint_doc_end>();
100
101    scope.define_func::<__breakpoint_call_start_handle>();
102    scope.define_func::<__breakpoint_call_end_handle>();
103    scope.define_func::<__breakpoint_function_handle>();
104    scope.define_func::<__breakpoint_break_handle>();
105    scope.define_func::<__breakpoint_continue_handle>();
106    scope.define_func::<__breakpoint_return_handle>();
107    scope.define_func::<__breakpoint_block_start_handle>();
108    scope.define_func::<__breakpoint_block_end_handle>();
109    scope.define_func::<__breakpoint_show_start_handle>();
110    scope.define_func::<__breakpoint_show_end_handle>();
111    scope.define_func::<__breakpoint_doc_start_handle>();
112    scope.define_func::<__breakpoint_doc_end_handle>();
113
114    Arc::new(library)
115}
116
117#[derive(PartialEq, Eq, PartialOrd, Ord)]
118struct PackageSpecCmp<'a> {
119    /// The namespace the package lives in.
120    pub namespace: &'a EcoString,
121    /// The name of the package within its namespace.
122    pub name: &'a EcoString,
123    /// The package's version.
124    pub version: &'a PackageVersion,
125}
126
127impl<'a> From<&'a PackageSpec> for PackageSpecCmp<'a> {
128    fn from(spec: &'a PackageSpec) -> Self {
129        Self {
130            namespace: &spec.namespace,
131            name: &spec.name,
132            version: &spec.version,
133        }
134    }
135}