tinymist_debug/
lib.rs

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