salsa/lib.rs
1#![allow(clippy::type_complexity)]
2#![allow(clippy::question_mark)]
3#![warn(rust_2018_idioms)]
4#![warn(missing_docs)]
5
6//! The salsa crate is a crate for incremental recomputation. It
7//! permits you to define a "database" of queries with both inputs and
8//! values derived from those inputs; as you set the inputs, you can
9//! re-execute the derived queries and it will try to re-use results
10//! from previous invocations as appropriate.
11
12mod derived;
13mod doctest;
14mod durability;
15mod hash;
16mod input;
17mod intern_id;
18mod interned;
19mod lru;
20mod revision;
21mod runtime;
22mod storage;
23
24pub mod debug;
25/// Items in this module are public for implementation reasons,
26/// and are exempt from the SemVer guarantees.
27#[doc(hidden)]
28pub mod plumbing;
29
30use crate::plumbing::CycleRecoveryStrategy;
31use crate::plumbing::DerivedQueryStorageOps;
32use crate::plumbing::InputQueryStorageOps;
33use crate::plumbing::LruQueryStorageOps;
34use crate::plumbing::QueryStorageMassOps;
35use crate::plumbing::QueryStorageOps;
36pub use crate::revision::Revision;
37use std::fmt::{self, Debug};
38use std::hash::Hash;
39use std::panic::AssertUnwindSafe;
40use std::panic::{self, UnwindSafe};
41
42pub use crate::durability::Durability;
43pub use crate::intern_id::InternId;
44pub use crate::interned::InternKey;
45pub use crate::runtime::Runtime;
46pub use crate::runtime::RuntimeId;
47pub use crate::storage::Storage;
48
49/// The base trait which your "query context" must implement. Gives
50/// access to the salsa runtime, which you must embed into your query
51/// context (along with whatever other state you may require).
52pub trait Database: plumbing::DatabaseOps {
53 /// This function is invoked at key points in the salsa
54 /// runtime. It permits the database to be customized and to
55 /// inject logging or other custom behavior.
56 fn salsa_event(&self, event_fn: Event) {
57 #![allow(unused_variables)]
58 }
59
60 /// Starts unwinding the stack if the current revision is cancelled.
61 ///
62 /// This method can be called by query implementations that perform
63 /// potentially expensive computations, in order to speed up propagation of
64 /// cancellation.
65 ///
66 /// Cancellation will automatically be triggered by salsa on any query
67 /// invocation.
68 ///
69 /// This method should not be overridden by `Database` implementors. A
70 /// `salsa_event` is emitted when this method is called, so that should be
71 /// used instead.
72 #[inline]
73 fn unwind_if_cancelled(&self) {
74 let runtime = self.salsa_runtime();
75 self.salsa_event(Event {
76 runtime_id: runtime.id(),
77 kind: EventKind::WillCheckCancellation,
78 });
79
80 let current_revision = runtime.current_revision();
81 let pending_revision = runtime.pending_revision();
82 tracing::debug!(
83 "unwind_if_cancelled: current_revision={:?}, pending_revision={:?}",
84 current_revision,
85 pending_revision
86 );
87 if pending_revision > current_revision {
88 runtime.unwind_cancelled();
89 }
90 }
91
92 /// Gives access to the underlying salsa runtime.
93 ///
94 /// This method should not be overridden by `Database` implementors.
95 fn salsa_runtime(&self) -> &Runtime {
96 self.ops_salsa_runtime()
97 }
98
99 /// Gives access to the underlying salsa runtime.
100 ///
101 /// This method should not be overridden by `Database` implementors.
102 fn salsa_runtime_mut(&mut self) -> &mut Runtime {
103 self.ops_salsa_runtime_mut()
104 }
105}
106
107/// The `Event` struct identifies various notable things that can
108/// occur during salsa execution. Instances of this struct are given
109/// to `salsa_event`.
110pub struct Event {
111 /// The id of the snapshot that triggered the event. Usually
112 /// 1-to-1 with a thread, as well.
113 pub runtime_id: RuntimeId,
114
115 /// What sort of event was it.
116 pub kind: EventKind,
117}
118
119impl Event {
120 /// Returns a type that gives a user-readable debug output.
121 /// Use like `println!("{:?}", index.debug(db))`.
122 pub fn debug<'me, D: ?Sized>(&'me self, db: &'me D) -> impl std::fmt::Debug + 'me
123 where
124 D: plumbing::DatabaseOps,
125 {
126 EventDebug { event: self, db }
127 }
128}
129
130impl fmt::Debug for Event {
131 fn fmt(&self, fmt: &mut fmt::Formatter<'_>) -> fmt::Result {
132 fmt.debug_struct("Event")
133 .field("runtime_id", &self.runtime_id)
134 .field("kind", &self.kind)
135 .finish()
136 }
137}
138
139struct EventDebug<'me, D: ?Sized>
140where
141 D: plumbing::DatabaseOps,
142{
143 event: &'me Event,
144 db: &'me D,
145}
146
147impl<'me, D: ?Sized> fmt::Debug for EventDebug<'me, D>
148where
149 D: plumbing::DatabaseOps,
150{
151 fn fmt(&self, fmt: &mut fmt::Formatter<'_>) -> fmt::Result {
152 fmt.debug_struct("Event")
153 .field("runtime_id", &self.event.runtime_id)
154 .field("kind", &self.event.kind.debug(self.db))
155 .finish()
156 }
157}
158
159/// An enum identifying the various kinds of events that can occur.
160pub enum EventKind {
161 /// Occurs when we found that all inputs to a memoized value are
162 /// up-to-date and hence the value can be re-used without
163 /// executing the closure.
164 ///
165 /// Executes before the "re-used" value is returned.
166 DidValidateMemoizedValue {
167 /// The database-key for the affected value. Implements `Debug`.
168 database_key: DatabaseKeyIndex,
169 },
170
171 /// Indicates that another thread (with id `other_runtime_id`) is processing the
172 /// given query (`database_key`), so we will block until they
173 /// finish.
174 ///
175 /// Executes after we have registered with the other thread but
176 /// before they have answered us.
177 ///
178 /// (NB: you can find the `id` of the current thread via the
179 /// `salsa_runtime`)
180 WillBlockOn {
181 /// The id of the runtime we will block on.
182 other_runtime_id: RuntimeId,
183
184 /// The database-key for the affected value. Implements `Debug`.
185 database_key: DatabaseKeyIndex,
186 },
187
188 /// Indicates that the function for this query will be executed.
189 /// This is either because it has never executed before or because
190 /// its inputs may be out of date.
191 WillExecute {
192 /// The database-key for the affected value. Implements `Debug`.
193 database_key: DatabaseKeyIndex,
194 },
195
196 /// Indicates that `unwind_if_cancelled` was called and salsa will check if
197 /// the current revision has been cancelled.
198 WillCheckCancellation,
199}
200
201impl EventKind {
202 /// Returns a type that gives a user-readable debug output.
203 /// Use like `println!("{:?}", index.debug(db))`.
204 pub fn debug<'me, D: ?Sized>(&'me self, db: &'me D) -> impl std::fmt::Debug + 'me
205 where
206 D: plumbing::DatabaseOps,
207 {
208 EventKindDebug { kind: self, db }
209 }
210}
211
212impl fmt::Debug for EventKind {
213 fn fmt(&self, fmt: &mut fmt::Formatter<'_>) -> fmt::Result {
214 match self {
215 EventKind::DidValidateMemoizedValue { database_key } => fmt
216 .debug_struct("DidValidateMemoizedValue")
217 .field("database_key", database_key)
218 .finish(),
219 EventKind::WillBlockOn {
220 other_runtime_id,
221 database_key,
222 } => fmt
223 .debug_struct("WillBlockOn")
224 .field("other_runtime_id", other_runtime_id)
225 .field("database_key", database_key)
226 .finish(),
227 EventKind::WillExecute { database_key } => fmt
228 .debug_struct("WillExecute")
229 .field("database_key", database_key)
230 .finish(),
231 EventKind::WillCheckCancellation => fmt.debug_struct("WillCheckCancellation").finish(),
232 }
233 }
234}
235
236struct EventKindDebug<'me, D: ?Sized>
237where
238 D: plumbing::DatabaseOps,
239{
240 kind: &'me EventKind,
241 db: &'me D,
242}
243
244impl<'me, D: ?Sized> fmt::Debug for EventKindDebug<'me, D>
245where
246 D: plumbing::DatabaseOps,
247{
248 fn fmt(&self, fmt: &mut fmt::Formatter<'_>) -> fmt::Result {
249 match self.kind {
250 EventKind::DidValidateMemoizedValue { database_key } => fmt
251 .debug_struct("DidValidateMemoizedValue")
252 .field("database_key", &database_key.debug(self.db))
253 .finish(),
254 EventKind::WillBlockOn {
255 other_runtime_id,
256 database_key,
257 } => fmt
258 .debug_struct("WillBlockOn")
259 .field("other_runtime_id", &other_runtime_id)
260 .field("database_key", &database_key.debug(self.db))
261 .finish(),
262 EventKind::WillExecute { database_key } => fmt
263 .debug_struct("WillExecute")
264 .field("database_key", &database_key.debug(self.db))
265 .finish(),
266 EventKind::WillCheckCancellation => fmt.debug_struct("WillCheckCancellation").finish(),
267 }
268 }
269}
270
271/// Indicates a database that also supports parallel query
272/// evaluation. All of Salsa's base query support is capable of
273/// parallel execution, but for it to work, your query key/value types
274/// must also be `Send`, as must any additional data in your database.
275pub trait ParallelDatabase: Database + Send {
276 /// Creates a second handle to the database that holds the
277 /// database fixed at a particular revision. So long as this
278 /// "frozen" handle exists, any attempt to [`set`] an input will
279 /// block.
280 ///
281 /// [`set`]: struct.QueryTable.html#method.set
282 ///
283 /// This is the method you are meant to use most of the time in a
284 /// parallel setting where modifications may arise asynchronously
285 /// (e.g., a language server). In this context, it is common to
286 /// wish to "fork off" a snapshot of the database performing some
287 /// series of queries in parallel and arranging the results. Using
288 /// this method for that purpose ensures that those queries will
289 /// see a consistent view of the database (it is also advisable
290 /// for those queries to use the [`Runtime::unwind_if_cancelled`]
291 /// method to check for cancellation).
292 ///
293 /// # Panics
294 ///
295 /// It is not permitted to create a snapshot from inside of a
296 /// query. Attepting to do so will panic.
297 ///
298 /// # Deadlock warning
299 ///
300 /// The intended pattern for snapshots is that, once created, they
301 /// are sent to another thread and used from there. As such, the
302 /// `snapshot` acquires a "read lock" on the database --
303 /// therefore, so long as the `snapshot` is not dropped, any
304 /// attempt to `set` a value in the database will block. If the
305 /// `snapshot` is owned by the same thread that is attempting to
306 /// `set`, this will cause a problem.
307 ///
308 /// # How to implement this
309 ///
310 /// Typically, this method will create a second copy of your
311 /// database type (`MyDatabaseType`, in the example below),
312 /// cloning over each of the fields from `self` into this new
313 /// copy. For the field that stores the salsa runtime, you should
314 /// use [the `Runtime::snapshot` method][rfm] to create a snapshot of the
315 /// runtime. Finally, package up the result using `Snapshot::new`,
316 /// which is a simple wrapper type that only gives `&self` access
317 /// to the database within (thus preventing the use of methods
318 /// that may mutate the inputs):
319 ///
320 /// [rfm]: struct.Runtime.html#method.snapshot
321 ///
322 /// ```rust,ignore
323 /// impl ParallelDatabase for MyDatabaseType {
324 /// fn snapshot(&self) -> Snapshot<Self> {
325 /// Snapshot::new(
326 /// MyDatabaseType {
327 /// runtime: self.runtime.snapshot(self),
328 /// other_field: self.other_field.clone(),
329 /// }
330 /// )
331 /// }
332 /// }
333 /// ```
334 fn snapshot(&self) -> Snapshot<Self>;
335}
336
337/// Simple wrapper struct that takes ownership of a database `DB` and
338/// only gives `&self` access to it. See [the `snapshot` method][fm]
339/// for more details.
340///
341/// [fm]: trait.ParallelDatabase.html#method.snapshot
342#[derive(Debug)]
343pub struct Snapshot<DB: ?Sized>
344where
345 DB: ParallelDatabase,
346{
347 db: DB,
348}
349
350impl<DB> Snapshot<DB>
351where
352 DB: ParallelDatabase,
353{
354 /// Creates a `Snapshot` that wraps the given database handle
355 /// `db`. From this point forward, only shared references to `db`
356 /// will be possible.
357 pub fn new(db: DB) -> Self {
358 Snapshot { db }
359 }
360}
361
362impl<DB> std::ops::Deref for Snapshot<DB>
363where
364 DB: ParallelDatabase,
365{
366 type Target = DB;
367
368 fn deref(&self) -> &DB {
369 &self.db
370 }
371}
372
373/// An integer that uniquely identifies a particular query instance within the
374/// database. Used to track dependencies between queries. Fully ordered and
375/// equatable but those orderings are arbitrary, and meant to be used only for
376/// inserting into maps and the like.
377#[derive(Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash, Debug)]
378pub struct DatabaseKeyIndex {
379 group_index: u16,
380 query_index: u16,
381 key_index: u32,
382}
383
384impl DatabaseKeyIndex {
385 /// Returns the index of the query group containing this key.
386 #[inline]
387 pub fn group_index(self) -> u16 {
388 self.group_index
389 }
390
391 /// Returns the index of the query within its query group.
392 #[inline]
393 pub fn query_index(self) -> u16 {
394 self.query_index
395 }
396
397 /// Returns the index of this particular query key within the query.
398 #[inline]
399 pub fn key_index(self) -> u32 {
400 self.key_index
401 }
402
403 /// Returns a type that gives a user-readable debug output.
404 /// Use like `println!("{:?}", index.debug(db))`.
405 pub fn debug<D: ?Sized>(self, db: &D) -> impl std::fmt::Debug + '_
406 where
407 D: plumbing::DatabaseOps,
408 {
409 DatabaseKeyIndexDebug { index: self, db }
410 }
411}
412
413/// Helper type for `DatabaseKeyIndex::debug`
414struct DatabaseKeyIndexDebug<'me, D: ?Sized>
415where
416 D: plumbing::DatabaseOps,
417{
418 index: DatabaseKeyIndex,
419 db: &'me D,
420}
421
422impl<D: ?Sized> std::fmt::Debug for DatabaseKeyIndexDebug<'_, D>
423where
424 D: plumbing::DatabaseOps,
425{
426 fn fmt(&self, fmt: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
427 self.db.fmt_index(self.index, fmt)
428 }
429}
430
431/// Trait implements by all of the "special types" associated with
432/// each of your queries.
433///
434/// Base trait of `Query` that has a lifetime parameter to allow the `DynDb` to be non-'static.
435pub trait QueryDb<'d>: Sized {
436 /// Dyn version of the associated trait for this query group.
437 type DynDb: ?Sized + Database + HasQueryGroup<Self::Group> + 'd;
438
439 /// Associate query group struct.
440 type Group: plumbing::QueryGroup<GroupStorage = Self::GroupStorage>;
441
442 /// Generated struct that contains storage for all queries in a group.
443 type GroupStorage;
444}
445
446/// Trait implements by all of the "special types" associated with
447/// each of your queries.
448pub trait Query: Debug + Default + Sized + for<'d> QueryDb<'d> {
449 /// Type that you you give as a parameter -- for queries with zero
450 /// or more than one input, this will be a tuple.
451 type Key: Clone + Debug + Hash + Eq;
452
453 /// What value does the query return?
454 type Value: Clone + Debug;
455
456 /// Internal struct storing the values for the query.
457 // type Storage: plumbing::QueryStorageOps<Self>;
458 type Storage;
459
460 /// A unique index identifying this query within the group.
461 const QUERY_INDEX: u16;
462
463 /// Name of the query method (e.g., `foo`)
464 const QUERY_NAME: &'static str;
465
466 /// Extact storage for this query from the storage for its group.
467 fn query_storage<'a>(
468 group_storage: &'a <Self as QueryDb<'_>>::GroupStorage,
469 ) -> &'a std::sync::Arc<Self::Storage>;
470
471 /// Extact storage for this query from the storage for its group.
472 fn query_storage_mut<'a>(
473 group_storage: &'a <Self as QueryDb<'_>>::GroupStorage,
474 ) -> &'a std::sync::Arc<Self::Storage>;
475}
476
477/// Return value from [the `query` method] on `Database`.
478/// Gives access to various less common operations on queries.
479///
480/// [the `query` method]: trait.Database.html#method.query
481pub struct QueryTable<'me, Q>
482where
483 Q: Query,
484{
485 db: &'me <Q as QueryDb<'me>>::DynDb,
486 storage: &'me Q::Storage,
487}
488
489impl<'me, Q> QueryTable<'me, Q>
490where
491 Q: Query,
492 Q::Storage: QueryStorageOps<Q>,
493{
494 /// Constructs a new `QueryTable`.
495 pub fn new(db: &'me <Q as QueryDb<'me>>::DynDb, storage: &'me Q::Storage) -> Self {
496 Self { db, storage }
497 }
498
499 /// Execute the query on a given input. Usually it's easier to
500 /// invoke the trait method directly. Note that for variadic
501 /// queries (those with no inputs, or those with more than one
502 /// input) the key will be a tuple.
503 pub fn get(&self, key: Q::Key) -> Q::Value {
504 self.storage.fetch(self.db, &key)
505 }
506
507 /// Completely clears the storage for this query.
508 ///
509 /// This method breaks internal invariants of salsa, so any further queries
510 /// might return nonsense results. It is useful only in very specific
511 /// circumstances -- for example, when one wants to observe which values
512 /// dropped together with the table
513 pub fn purge(&self)
514 where
515 Q::Storage: plumbing::QueryStorageMassOps,
516 {
517 self.storage.purge();
518 }
519}
520
521/// Return value from [the `query_mut` method] on `Database`.
522/// Gives access to the `set` method, notably, that is used to
523/// set the value of an input query.
524///
525/// [the `query_mut` method]: trait.Database.html#method.query_mut
526pub struct QueryTableMut<'me, Q>
527where
528 Q: Query + 'me,
529{
530 runtime: &'me mut Runtime,
531 storage: &'me Q::Storage,
532}
533
534impl<'me, Q> QueryTableMut<'me, Q>
535where
536 Q: Query,
537{
538 /// Constructs a new `QueryTableMut`.
539 pub fn new(runtime: &'me mut Runtime, storage: &'me Q::Storage) -> Self {
540 Self { runtime, storage }
541 }
542
543 /// Assign a value to an "input query". Must be used outside of
544 /// an active query computation.
545 ///
546 /// If you are using `snapshot`, see the notes on blocking
547 /// and cancellation on [the `query_mut` method].
548 ///
549 /// [the `query_mut` method]: trait.Database.html#method.query_mut
550 pub fn set(&mut self, key: Q::Key, value: Q::Value)
551 where
552 Q::Storage: plumbing::InputQueryStorageOps<Q>,
553 {
554 self.set_with_durability(key, value, Durability::LOW);
555 }
556
557 /// Assign a value to an "input query", with the additional
558 /// promise that this value will **never change**. Must be used
559 /// outside of an active query computation.
560 ///
561 /// If you are using `snapshot`, see the notes on blocking
562 /// and cancellation on [the `query_mut` method].
563 ///
564 /// [the `query_mut` method]: trait.Database.html#method.query_mut
565 pub fn set_with_durability(&mut self, key: Q::Key, value: Q::Value, durability: Durability)
566 where
567 Q::Storage: plumbing::InputQueryStorageOps<Q>,
568 {
569 self.storage.set(self.runtime, &key, value, durability);
570 }
571
572 /// Sets the size of LRU cache of values for this query table.
573 ///
574 /// That is, at most `cap` values will be preset in the table at the same
575 /// time. This helps with keeping maximum memory usage under control, at the
576 /// cost of potential extra recalculations of evicted values.
577 ///
578 /// If `cap` is zero, all values are preserved, this is the default.
579 pub fn set_lru_capacity(&self, cap: usize)
580 where
581 Q::Storage: plumbing::LruQueryStorageOps,
582 {
583 self.storage.set_lru_capacity(cap);
584 }
585
586 /// Marks the computed value as outdated.
587 ///
588 /// This causes salsa to re-execute the query function on the next access to
589 /// the query, even if all dependencies are up to date.
590 ///
591 /// This is most commonly used as part of the [on-demand input
592 /// pattern](https://salsa-rs.github.io/salsa/common_patterns/on_demand_inputs.html).
593 pub fn invalidate(&mut self, key: &Q::Key)
594 where
595 Q::Storage: plumbing::DerivedQueryStorageOps<Q>,
596 {
597 self.storage.invalidate(self.runtime, key)
598 }
599}
600
601/// A panic payload indicating that execution of a salsa query was cancelled.
602///
603/// This can occur for a few reasons:
604/// *
605/// *
606/// *
607#[derive(Debug)]
608#[non_exhaustive]
609pub enum Cancelled {
610 /// The query was operating on revision R, but there is a pending write to move to revision R+1.
611 #[non_exhaustive]
612 PendingWrite,
613
614 /// The query was blocked on another thread, and that thread panicked.
615 #[non_exhaustive]
616 PropagatedPanic,
617}
618
619impl Cancelled {
620 fn throw(self) -> ! {
621 // We use resume and not panic here to avoid running the panic
622 // hook (that is, to avoid collecting and printing backtrace).
623 std::panic::resume_unwind(Box::new(self));
624 }
625
626 /// Runs `f`, and catches any salsa cancellation.
627 pub fn catch<F, T>(f: F) -> Result<T, Cancelled>
628 where
629 F: FnOnce() -> T + UnwindSafe,
630 {
631 match panic::catch_unwind(f) {
632 Ok(t) => Ok(t),
633 Err(payload) => match payload.downcast() {
634 Ok(cancelled) => Err(*cancelled),
635 Err(payload) => panic::resume_unwind(payload),
636 },
637 }
638 }
639}
640
641impl std::fmt::Display for Cancelled {
642 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
643 let why = match self {
644 Cancelled::PendingWrite => "pending write",
645 Cancelled::PropagatedPanic => "propagated panic",
646 };
647 f.write_str("cancelled because of ")?;
648 f.write_str(why)
649 }
650}
651
652impl std::error::Error for Cancelled {}
653
654/// Captures the participants of a cycle that occurred when executing a query.
655///
656/// This type is meant to be used to help give meaningful error messages to the
657/// user or to help salsa developers figure out why their program is resulting
658/// in a computation cycle.
659///
660/// It is used in a few ways:
661///
662/// * During [cycle recovery](https://https://salsa-rs.github.io/salsa/cycles/fallback.html),
663/// where it is given to the fallback function.
664/// * As the panic value when an unexpected cycle (i.e., a cycle where one or more participants
665/// lacks cycle recovery information) occurs.
666///
667/// You can read more about cycle handling in
668/// the [salsa book](https://https://salsa-rs.github.io/salsa/cycles.html).
669#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
670pub struct Cycle {
671 participants: plumbing::CycleParticipants,
672}
673
674impl Cycle {
675 pub(crate) fn new(participants: plumbing::CycleParticipants) -> Self {
676 Self { participants }
677 }
678
679 /// True if two `Cycle` values represent the same cycle.
680 pub(crate) fn is(&self, cycle: &Cycle) -> bool {
681 triomphe::Arc::ptr_eq(&self.participants, &cycle.participants)
682 }
683
684 pub(crate) fn throw(self) -> ! {
685 tracing::debug!("throwing cycle {:?}", self);
686 std::panic::resume_unwind(Box::new(self))
687 }
688
689 pub(crate) fn catch<T>(execute: impl FnOnce() -> T) -> Result<T, Cycle> {
690 match std::panic::catch_unwind(AssertUnwindSafe(execute)) {
691 Ok(v) => Ok(v),
692 Err(err) => match err.downcast::<Cycle>() {
693 Ok(cycle) => Err(*cycle),
694 Err(other) => std::panic::resume_unwind(other),
695 },
696 }
697 }
698
699 /// Iterate over the [`DatabaseKeyIndex`] for each query participating
700 /// in the cycle. The start point of this iteration within the cycle
701 /// is arbitrary but deterministic, but the ordering is otherwise determined
702 /// by the execution.
703 pub fn participant_keys(&self) -> impl Iterator<Item = DatabaseKeyIndex> + '_ {
704 self.participants.iter().copied()
705 }
706
707 /// Returns a vector with the debug information for
708 /// all the participants in the cycle.
709 pub fn all_participants<DB: ?Sized + Database>(&self, db: &DB) -> Vec<String> {
710 self.participant_keys()
711 .map(|d| format!("{:?}", d.debug(db)))
712 .collect()
713 }
714
715 /// Returns a vector with the debug information for
716 /// those participants in the cycle that lacked recovery
717 /// information.
718 pub fn unexpected_participants<DB: ?Sized + Database>(&self, db: &DB) -> Vec<String> {
719 self.participant_keys()
720 .filter(|&d| db.cycle_recovery_strategy(d) == CycleRecoveryStrategy::Panic)
721 .map(|d| format!("{:?}", d.debug(db)))
722 .collect()
723 }
724
725 /// Returns a "debug" view onto this strict that can be used to print out information.
726 pub fn debug<'me, DB: ?Sized + Database>(&'me self, db: &'me DB) -> impl std::fmt::Debug + 'me {
727 struct UnexpectedCycleDebug<'me> {
728 c: &'me Cycle,
729 db: &'me dyn Database,
730 }
731
732 impl<'me> std::fmt::Debug for UnexpectedCycleDebug<'me> {
733 fn fmt(&self, fmt: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
734 fmt.debug_struct("UnexpectedCycle")
735 .field("all_participants", &self.c.all_participants(self.db))
736 .field(
737 "unexpected_participants",
738 &self.c.unexpected_participants(self.db),
739 )
740 .finish()
741 }
742 }
743
744 UnexpectedCycleDebug {
745 c: self,
746 db: db.ops_database(),
747 }
748 }
749}
750
751// Re-export the procedural macros.
752#[allow(unused_imports)]
753#[macro_use]
754extern crate salsa_macros;
755use plumbing::HasQueryGroup;
756pub use salsa_macros::*;