mirror of
https://github.com/salsa-rs/salsa.git
synced 2025-02-08 21:35:47 +00:00
Merge pull request #482 from nikomatsakis/debug-with-db-improvements
[breaking-salsa-2022] Debug with db: ignore deps, support customization
This commit is contained in:
commit
bdf7d9ca50
16 changed files with 332 additions and 210 deletions
|
@ -1,4 +1,4 @@
|
||||||
use crate::salsa_struct::{SalsaField, SalsaStruct, SalsaStructKind};
|
use crate::salsa_struct::{SalsaField, SalsaStruct};
|
||||||
use proc_macro2::{Literal, TokenStream};
|
use proc_macro2::{Literal, TokenStream};
|
||||||
|
|
||||||
/// For an entity struct `Foo` with fields `f1: T1, ..., fN: TN`, we generate...
|
/// For an entity struct `Foo` with fields `f1: T1, ..., fN: TN`, we generate...
|
||||||
|
@ -10,9 +10,7 @@ pub(crate) fn input(
|
||||||
args: proc_macro::TokenStream,
|
args: proc_macro::TokenStream,
|
||||||
input: proc_macro::TokenStream,
|
input: proc_macro::TokenStream,
|
||||||
) -> proc_macro::TokenStream {
|
) -> proc_macro::TokenStream {
|
||||||
match SalsaStruct::new(SalsaStructKind::Input, args, input)
|
match SalsaStruct::new(args, input).and_then(|el| InputStruct(el).generate_input()) {
|
||||||
.and_then(|el| InputStruct(el).generate_input())
|
|
||||||
{
|
|
||||||
Ok(s) => s.into(),
|
Ok(s) => s.into(),
|
||||||
Err(err) => err.into_compile_error().into(),
|
Err(err) => err.into_compile_error().into(),
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
use crate::salsa_struct::{SalsaStruct, SalsaStructKind};
|
use crate::salsa_struct::SalsaStruct;
|
||||||
use proc_macro2::TokenStream;
|
use proc_macro2::TokenStream;
|
||||||
|
|
||||||
// #[salsa::interned(jar = Jar0, data = TyData0)]
|
// #[salsa::interned(jar = Jar0, data = TyData0)]
|
||||||
|
@ -13,9 +13,7 @@ pub(crate) fn interned(
|
||||||
args: proc_macro::TokenStream,
|
args: proc_macro::TokenStream,
|
||||||
input: proc_macro::TokenStream,
|
input: proc_macro::TokenStream,
|
||||||
) -> proc_macro::TokenStream {
|
) -> proc_macro::TokenStream {
|
||||||
match SalsaStruct::new(SalsaStructKind::Interned, args, input)
|
match SalsaStruct::new(args, input).and_then(|el| InternedStruct(el).generate_interned()) {
|
||||||
.and_then(|el| InternedStruct(el).generate_interned())
|
|
||||||
{
|
|
||||||
Ok(s) => s.into(),
|
Ok(s) => s.into(),
|
||||||
Err(err) => err.into_compile_error().into(),
|
Err(err) => err.into_compile_error().into(),
|
||||||
}
|
}
|
||||||
|
|
|
@ -29,50 +29,72 @@ use crate::options::{AllowedOptions, Options};
|
||||||
use proc_macro2::{Ident, Span, TokenStream};
|
use proc_macro2::{Ident, Span, TokenStream};
|
||||||
use syn::spanned::Spanned;
|
use syn::spanned::Spanned;
|
||||||
|
|
||||||
pub(crate) enum SalsaStructKind {
|
|
||||||
Input,
|
|
||||||
Tracked,
|
|
||||||
Interned,
|
|
||||||
}
|
|
||||||
|
|
||||||
pub(crate) struct SalsaStruct<A: AllowedOptions> {
|
pub(crate) struct SalsaStruct<A: AllowedOptions> {
|
||||||
kind: SalsaStructKind,
|
|
||||||
args: Options<A>,
|
args: Options<A>,
|
||||||
struct_item: syn::ItemStruct,
|
struct_item: syn::ItemStruct,
|
||||||
|
customizations: Vec<Customization>,
|
||||||
fields: Vec<SalsaField>,
|
fields: Vec<SalsaField>,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(PartialEq, Eq, Debug, Copy, Clone)]
|
||||||
|
pub enum Customization {
|
||||||
|
DebugWithDb,
|
||||||
|
}
|
||||||
|
|
||||||
const BANNED_FIELD_NAMES: &[&str] = &["from", "new"];
|
const BANNED_FIELD_NAMES: &[&str] = &["from", "new"];
|
||||||
|
|
||||||
impl<A: AllowedOptions> SalsaStruct<A> {
|
impl<A: AllowedOptions> SalsaStruct<A> {
|
||||||
pub(crate) fn new(
|
pub(crate) fn new(
|
||||||
kind: SalsaStructKind,
|
|
||||||
args: proc_macro::TokenStream,
|
args: proc_macro::TokenStream,
|
||||||
input: proc_macro::TokenStream,
|
input: proc_macro::TokenStream,
|
||||||
) -> syn::Result<Self> {
|
) -> syn::Result<Self> {
|
||||||
let struct_item = syn::parse(input)?;
|
let struct_item = syn::parse(input)?;
|
||||||
Self::with_struct(kind, args, struct_item)
|
Self::with_struct(args, struct_item)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub(crate) fn with_struct(
|
pub(crate) fn with_struct(
|
||||||
kind: SalsaStructKind,
|
|
||||||
args: proc_macro::TokenStream,
|
args: proc_macro::TokenStream,
|
||||||
struct_item: syn::ItemStruct,
|
struct_item: syn::ItemStruct,
|
||||||
) -> syn::Result<Self> {
|
) -> syn::Result<Self> {
|
||||||
let args: Options<A> = syn::parse(args)?;
|
let args: Options<A> = syn::parse(args)?;
|
||||||
let fields = Self::extract_options(&struct_item)?;
|
let customizations = Self::extract_customizations(&struct_item)?;
|
||||||
|
let fields = Self::extract_fields(&struct_item)?;
|
||||||
Ok(Self {
|
Ok(Self {
|
||||||
kind,
|
|
||||||
args,
|
args,
|
||||||
struct_item,
|
struct_item,
|
||||||
|
customizations,
|
||||||
fields,
|
fields,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn extract_customizations(struct_item: &syn::ItemStruct) -> syn::Result<Vec<Customization>> {
|
||||||
|
Ok(struct_item
|
||||||
|
.attrs
|
||||||
|
.iter()
|
||||||
|
.map(|attr| {
|
||||||
|
if attr.path.is_ident("customize") {
|
||||||
|
// FIXME: this should be a comma separated list but I couldn't
|
||||||
|
// be bothered to remember how syn does this.
|
||||||
|
let args: syn::Ident = attr.parse_args()?;
|
||||||
|
if args.to_string() == "DebugWithDb" {
|
||||||
|
Ok(vec![Customization::DebugWithDb])
|
||||||
|
} else {
|
||||||
|
Err(syn::Error::new_spanned(args, "unrecognized customization"))
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
Ok(vec![])
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.collect::<Result<Vec<Vec<_>>, _>>()?
|
||||||
|
.into_iter()
|
||||||
|
.flatten()
|
||||||
|
.collect())
|
||||||
|
}
|
||||||
|
|
||||||
/// Extract out the fields and their options:
|
/// Extract out the fields and their options:
|
||||||
/// If this is a struct, it must use named fields, so we can define field accessors.
|
/// If this is a struct, it must use named fields, so we can define field accessors.
|
||||||
/// If it is an enum, then this is not necessary.
|
/// If it is an enum, then this is not necessary.
|
||||||
pub(crate) fn extract_options(struct_item: &syn::ItemStruct) -> syn::Result<Vec<SalsaField>> {
|
fn extract_fields(struct_item: &syn::ItemStruct) -> syn::Result<Vec<SalsaField>> {
|
||||||
match &struct_item.fields {
|
match &struct_item.fields {
|
||||||
syn::Fields::Named(n) => Ok(n
|
syn::Fields::Named(n) => Ok(n
|
||||||
.named
|
.named
|
||||||
|
@ -93,13 +115,6 @@ impl<A: AllowedOptions> SalsaStruct<A> {
|
||||||
self.fields.iter()
|
self.fields.iter()
|
||||||
}
|
}
|
||||||
|
|
||||||
pub(crate) fn is_identity_field(&self, field: &SalsaField) -> bool {
|
|
||||||
match self.kind {
|
|
||||||
SalsaStructKind::Input | SalsaStructKind::Tracked => field.has_id_attr,
|
|
||||||
SalsaStructKind::Interned => true,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Names of all fields (id and value).
|
/// Names of all fields (id and value).
|
||||||
///
|
///
|
||||||
/// If this is an enum, empty vec.
|
/// If this is an enum, empty vec.
|
||||||
|
@ -163,12 +178,14 @@ impl<A: AllowedOptions> SalsaStruct<A> {
|
||||||
let ident = self.id_ident();
|
let ident = self.id_ident();
|
||||||
let visibility = &self.struct_item.vis;
|
let visibility = &self.struct_item.vis;
|
||||||
|
|
||||||
// Extract the attributes the user gave, but screen out derive, since we are adding our own.
|
// Extract the attributes the user gave, but screen out derive, since we are adding our own,
|
||||||
|
// and the customize attribute that we use for our own purposes.
|
||||||
let attrs: Vec<_> = self
|
let attrs: Vec<_> = self
|
||||||
.struct_item
|
.struct_item
|
||||||
.attrs
|
.attrs
|
||||||
.iter()
|
.iter()
|
||||||
.filter(|attr| !attr.path.is_ident("derive"))
|
.filter(|attr| !attr.path.is_ident("derive"))
|
||||||
|
.filter(|attr| !attr.path.is_ident("customize"))
|
||||||
.collect();
|
.collect();
|
||||||
|
|
||||||
parse_quote! {
|
parse_quote! {
|
||||||
|
@ -231,13 +248,17 @@ impl<A: AllowedOptions> SalsaStruct<A> {
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Generate `impl salsa::DebugWithDb for Foo`
|
/// Generate `impl salsa::DebugWithDb for Foo`
|
||||||
pub(crate) fn as_debug_with_db_impl(&self) -> syn::ItemImpl {
|
pub(crate) fn as_debug_with_db_impl(&self) -> Option<syn::ItemImpl> {
|
||||||
|
if self.customizations.contains(&Customization::DebugWithDb) {
|
||||||
|
return None;
|
||||||
|
}
|
||||||
|
|
||||||
let ident = self.id_ident();
|
let ident = self.id_ident();
|
||||||
|
|
||||||
let db_type = self.db_dyn_ty();
|
let db_type = self.db_dyn_ty();
|
||||||
let ident_string = ident.to_string();
|
let ident_string = ident.to_string();
|
||||||
|
|
||||||
// `::salsa::debug::helper::SalsaDebug` will use `DebugWithDb` or fallbak to `Debug`
|
// `::salsa::debug::helper::SalsaDebug` will use `DebugWithDb` or fallback to `Debug`
|
||||||
let fields = self
|
let fields = self
|
||||||
.all_fields()
|
.all_fields()
|
||||||
.map(|field| -> TokenStream {
|
.map(|field| -> TokenStream {
|
||||||
|
@ -245,36 +266,23 @@ impl<A: AllowedOptions> SalsaStruct<A> {
|
||||||
let field_getter = field.get_name();
|
let field_getter = field.get_name();
|
||||||
let field_ty = field.ty();
|
let field_ty = field.ty();
|
||||||
|
|
||||||
let field_debug = quote_spanned! { field.field.span() =>
|
quote_spanned! { field.field.span() =>
|
||||||
debug_struct = debug_struct.field(
|
debug_struct = debug_struct.field(
|
||||||
#field_name_string,
|
#field_name_string,
|
||||||
&::salsa::debug::helper::SalsaDebug::<#field_ty, #db_type>::salsa_debug(
|
&::salsa::debug::helper::SalsaDebug::<#field_ty, #db_type>::salsa_debug(
|
||||||
#[allow(clippy::needless_borrow)]
|
#[allow(clippy::needless_borrow)]
|
||||||
&self.#field_getter(_db),
|
&self.#field_getter(_db),
|
||||||
_db,
|
_db,
|
||||||
_include_all_fields
|
|
||||||
)
|
)
|
||||||
);
|
);
|
||||||
};
|
|
||||||
|
|
||||||
if self.is_identity_field(field) {
|
|
||||||
quote_spanned! { field.field.span() =>
|
|
||||||
#field_debug
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
quote_spanned! { field.field.span() =>
|
|
||||||
if _include_all_fields {
|
|
||||||
#field_debug
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
.collect::<TokenStream>();
|
.collect::<TokenStream>();
|
||||||
|
|
||||||
// `use ::salsa::debug::helper::Fallback` is needed for the fallback to `Debug` impl
|
// `use ::salsa::debug::helper::Fallback` is needed for the fallback to `Debug` impl
|
||||||
parse_quote_spanned! {ident.span()=>
|
Some(parse_quote_spanned! {ident.span()=>
|
||||||
impl ::salsa::DebugWithDb<#db_type> for #ident {
|
impl ::salsa::DebugWithDb<#db_type> for #ident {
|
||||||
fn fmt(&self, f: &mut ::std::fmt::Formatter<'_>, _db: &#db_type, _include_all_fields: bool) -> ::std::fmt::Result {
|
fn fmt(&self, f: &mut ::std::fmt::Formatter<'_>, _db: &#db_type) -> ::std::fmt::Result {
|
||||||
#[allow(unused_imports)]
|
#[allow(unused_imports)]
|
||||||
use ::salsa::debug::helper::Fallback;
|
use ::salsa::debug::helper::Fallback;
|
||||||
let mut debug_struct = &mut f.debug_struct(#ident_string);
|
let mut debug_struct = &mut f.debug_struct(#ident_string);
|
||||||
|
@ -283,7 +291,7 @@ impl<A: AllowedOptions> SalsaStruct<A> {
|
||||||
debug_struct.finish()
|
debug_struct.finish()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Disallow `#[id]` attributes on the fields of this struct.
|
/// Disallow `#[id]` attributes on the fields of this struct.
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
use proc_macro2::{Literal, Span, TokenStream};
|
use proc_macro2::{Literal, Span, TokenStream};
|
||||||
|
|
||||||
use crate::salsa_struct::{SalsaField, SalsaStruct, SalsaStructKind};
|
use crate::salsa_struct::{SalsaField, SalsaStruct};
|
||||||
|
|
||||||
/// For an tracked struct `Foo` with fields `f1: T1, ..., fN: TN`, we generate...
|
/// For an tracked struct `Foo` with fields `f1: T1, ..., fN: TN`, we generate...
|
||||||
///
|
///
|
||||||
|
@ -11,8 +11,7 @@ pub(crate) fn tracked(
|
||||||
args: proc_macro::TokenStream,
|
args: proc_macro::TokenStream,
|
||||||
struct_item: syn::ItemStruct,
|
struct_item: syn::ItemStruct,
|
||||||
) -> syn::Result<TokenStream> {
|
) -> syn::Result<TokenStream> {
|
||||||
SalsaStruct::with_struct(SalsaStructKind::Tracked, args, struct_item)
|
SalsaStruct::with_struct(args, struct_item).and_then(|el| TrackedStruct(el).generate_tracked())
|
||||||
.and_then(|el| TrackedStruct(el).generate_tracked())
|
|
||||||
}
|
}
|
||||||
|
|
||||||
struct TrackedStruct(SalsaStruct<Self>);
|
struct TrackedStruct(SalsaStruct<Self>);
|
||||||
|
|
|
@ -5,7 +5,37 @@ use std::{
|
||||||
sync::Arc,
|
sync::Arc,
|
||||||
};
|
};
|
||||||
|
|
||||||
pub trait DebugWithDb<Db: ?Sized> {
|
use crate::database::AsSalsaDatabase;
|
||||||
|
|
||||||
|
/// `DebugWithDb` is a version of the traditional [`Debug`](`std::fmt::Debug`)
|
||||||
|
/// trait that gives access to the salsa database, allowing tracked
|
||||||
|
/// structs to print the values of their fields. It is typically not used
|
||||||
|
/// directly, instead you should write (e.g.) `format!("{:?}", foo.debug(db))`.
|
||||||
|
/// Implementations are automatically provided for `#[salsa::tracked]`
|
||||||
|
/// items, though you can opt-out from that if you wish to provide a manual
|
||||||
|
/// implementation.
|
||||||
|
///
|
||||||
|
/// # WARNING: Intended for debug use only!
|
||||||
|
///
|
||||||
|
/// Debug print-outs of tracked structs include the value of all their fields,
|
||||||
|
/// but the reads of those fields are ignored by salsa. This avoids creating
|
||||||
|
/// spurious dependencies from debugging code, but if you use the resulting
|
||||||
|
/// string to influence the outputs (return value, accumulators, etc) from your
|
||||||
|
/// query, salsa's dependency tracking will be undermined.
|
||||||
|
///
|
||||||
|
/// If for some reason you *want* to incorporate dependency output into
|
||||||
|
/// your query, do not use the `debug` or `into_debug` helpers and instead
|
||||||
|
/// invoke `fmt` manually.
|
||||||
|
pub trait DebugWithDb<Db: ?Sized + AsSalsaDatabase> {
|
||||||
|
/// Creates a wrapper type that implements `Debug` but which
|
||||||
|
/// uses the `DebugWithDb::fmt`.
|
||||||
|
///
|
||||||
|
/// # WARNING: Intended for debug use only!
|
||||||
|
///
|
||||||
|
/// The wrapper type Debug impl will access the value of all
|
||||||
|
/// fields but those accesses are ignored by salsa. This is only
|
||||||
|
/// suitable for debug output. See [`DebugWithDb`][] trait comment
|
||||||
|
/// for more details.
|
||||||
fn debug<'me, 'db>(&'me self, db: &'me Db) -> DebugWith<'me, Db>
|
fn debug<'me, 'db>(&'me self, db: &'me Db) -> DebugWith<'me, Db>
|
||||||
where
|
where
|
||||||
Self: Sized + 'me,
|
Self: Sized + 'me,
|
||||||
|
@ -13,35 +43,18 @@ pub trait DebugWithDb<Db: ?Sized> {
|
||||||
DebugWith {
|
DebugWith {
|
||||||
value: BoxRef::Ref(self),
|
value: BoxRef::Ref(self),
|
||||||
db,
|
db,
|
||||||
include_all_fields: false,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn debug_with<'me, 'db>(&'me self, db: &'me Db, include_all_fields: bool) -> DebugWith<'me, Db>
|
|
||||||
where
|
|
||||||
Self: Sized + 'me,
|
|
||||||
{
|
|
||||||
DebugWith {
|
|
||||||
value: BoxRef::Ref(self),
|
|
||||||
db,
|
|
||||||
include_all_fields,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Be careful when using this method inside a tracked function,
|
|
||||||
/// because the default macro generated implementation will read all fields,
|
|
||||||
/// maybe introducing undesired dependencies.
|
|
||||||
fn debug_all<'me, 'db>(&'me self, db: &'me Db) -> DebugWith<'me, Db>
|
|
||||||
where
|
|
||||||
Self: Sized + 'me,
|
|
||||||
{
|
|
||||||
DebugWith {
|
|
||||||
value: BoxRef::Ref(self),
|
|
||||||
db,
|
|
||||||
include_all_fields: true,
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Creates a wrapper type that implements `Debug` but which
|
||||||
|
/// uses the `DebugWithDb::fmt`.
|
||||||
|
///
|
||||||
|
/// # WARNING: Intended for debug use only!
|
||||||
|
///
|
||||||
|
/// The wrapper type Debug impl will access the value of all
|
||||||
|
/// fields but those accesses are ignored by salsa. This is only
|
||||||
|
/// suitable for debug output. See [`DebugWithDb`][] trait comment
|
||||||
|
/// for more details.
|
||||||
fn into_debug<'me, 'db>(self, db: &'me Db) -> DebugWith<'me, Db>
|
fn into_debug<'me, 'db>(self, db: &'me Db) -> DebugWith<'me, Db>
|
||||||
where
|
where
|
||||||
Self: Sized + 'me,
|
Self: Sized + 'me,
|
||||||
|
@ -49,35 +62,33 @@ pub trait DebugWithDb<Db: ?Sized> {
|
||||||
DebugWith {
|
DebugWith {
|
||||||
value: BoxRef::Box(Box::new(self)),
|
value: BoxRef::Box(Box::new(self)),
|
||||||
db,
|
db,
|
||||||
include_all_fields: false,
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Be careful when using this method inside a tracked function,
|
/// Format `self` given the database `db`.
|
||||||
/// because the default macro generated implementation will read all fields,
|
///
|
||||||
/// maybe introducing undesired dependencies.
|
/// # Dependency tracking
|
||||||
fn into_debug_all<'me, 'db>(self, db: &'me Db) -> DebugWith<'me, Db>
|
///
|
||||||
where
|
/// When invoked manually, field accesses that occur
|
||||||
Self: Sized + 'me,
|
/// within this method are tracked by salsa. But when invoked
|
||||||
{
|
/// the [`DebugWith`][] value returned by the [`debug`](`Self::debug`)
|
||||||
DebugWith {
|
/// and [`into_debug`][`Self::into_debug`] methods,
|
||||||
value: BoxRef::Box(Box::new(self)),
|
/// those accesses are ignored.
|
||||||
db,
|
fn fmt(&self, f: &mut fmt::Formatter<'_>, db: &Db) -> fmt::Result;
|
||||||
include_all_fields: true,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// if `include_all_fields` is `false` only identity fields should be read, which means:
|
|
||||||
/// - for [#\[salsa::input\]](salsa_2022_macros::input) no fields
|
|
||||||
/// - for [#\[salsa::tracked\]](salsa_2022_macros::tracked) only fields with `#[id]` attribute
|
|
||||||
/// - for [#\[salsa::interned\]](salsa_2022_macros::interned) any field
|
|
||||||
fn fmt(&self, f: &mut fmt::Formatter<'_>, db: &Db, include_all_fields: bool) -> fmt::Result;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
pub struct DebugWith<'me, Db: ?Sized> {
|
/// Helper type for the [`DebugWithDb`][] trait that
|
||||||
|
/// wraps a value and implements [`std::fmt::Debug`][],
|
||||||
|
/// redirecting calls to the `fmt` method from [`DebugWithDb`][].
|
||||||
|
///
|
||||||
|
/// # WARNING: Intended for debug use only!
|
||||||
|
///
|
||||||
|
/// This type intentionally ignores salsa dependencies used
|
||||||
|
/// to generate the debug output. See the [`DebugWithDb`][] trait
|
||||||
|
/// for more notes on this.
|
||||||
|
pub struct DebugWith<'me, Db: ?Sized + AsSalsaDatabase> {
|
||||||
value: BoxRef<'me, dyn DebugWithDb<Db> + 'me>,
|
value: BoxRef<'me, dyn DebugWithDb<Db> + 'me>,
|
||||||
db: &'me Db,
|
db: &'me Db,
|
||||||
include_all_fields: bool,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
enum BoxRef<'me, T: ?Sized> {
|
enum BoxRef<'me, T: ?Sized> {
|
||||||
|
@ -96,54 +107,64 @@ impl<T: ?Sized> std::ops::Deref for BoxRef<'_, T> {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<D: ?Sized> fmt::Debug for DebugWith<'_, D> {
|
impl<Db: ?Sized> fmt::Debug for DebugWith<'_, Db>
|
||||||
|
where
|
||||||
|
Db: AsSalsaDatabase,
|
||||||
|
{
|
||||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||||
DebugWithDb::fmt(&*self.value, f, self.db, self.include_all_fields)
|
let db = self.db.as_salsa_database();
|
||||||
|
db.runtime()
|
||||||
|
.debug_probe(|| DebugWithDb::fmt(&*self.value, f, self.db))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<Db: ?Sized, T: ?Sized> DebugWithDb<Db> for &T
|
impl<Db: ?Sized, T: ?Sized> DebugWithDb<Db> for &T
|
||||||
where
|
where
|
||||||
T: DebugWithDb<Db>,
|
T: DebugWithDb<Db>,
|
||||||
|
Db: AsSalsaDatabase,
|
||||||
{
|
{
|
||||||
fn fmt(&self, f: &mut fmt::Formatter<'_>, db: &Db, include_all_fields: bool) -> fmt::Result {
|
fn fmt(&self, f: &mut fmt::Formatter<'_>, db: &Db) -> fmt::Result {
|
||||||
T::fmt(self, f, db, include_all_fields)
|
T::fmt(self, f, db)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<Db: ?Sized, T: ?Sized> DebugWithDb<Db> for Box<T>
|
impl<Db: ?Sized, T: ?Sized> DebugWithDb<Db> for Box<T>
|
||||||
where
|
where
|
||||||
T: DebugWithDb<Db>,
|
T: DebugWithDb<Db>,
|
||||||
|
Db: AsSalsaDatabase,
|
||||||
{
|
{
|
||||||
fn fmt(&self, f: &mut fmt::Formatter<'_>, db: &Db, include_all_fields: bool) -> fmt::Result {
|
fn fmt(&self, f: &mut fmt::Formatter<'_>, db: &Db) -> fmt::Result {
|
||||||
T::fmt(self, f, db, include_all_fields)
|
T::fmt(self, f, db)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<Db: ?Sized, T> DebugWithDb<Db> for Rc<T>
|
impl<Db: ?Sized, T> DebugWithDb<Db> for Rc<T>
|
||||||
where
|
where
|
||||||
T: DebugWithDb<Db>,
|
T: DebugWithDb<Db>,
|
||||||
|
Db: AsSalsaDatabase,
|
||||||
{
|
{
|
||||||
fn fmt(&self, f: &mut fmt::Formatter<'_>, db: &Db, include_all_fields: bool) -> fmt::Result {
|
fn fmt(&self, f: &mut fmt::Formatter<'_>, db: &Db) -> fmt::Result {
|
||||||
T::fmt(self, f, db, include_all_fields)
|
T::fmt(self, f, db)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<Db: ?Sized, T: ?Sized> DebugWithDb<Db> for Arc<T>
|
impl<Db: ?Sized, T: ?Sized> DebugWithDb<Db> for Arc<T>
|
||||||
where
|
where
|
||||||
T: DebugWithDb<Db>,
|
T: DebugWithDb<Db>,
|
||||||
|
Db: AsSalsaDatabase,
|
||||||
{
|
{
|
||||||
fn fmt(&self, f: &mut fmt::Formatter<'_>, db: &Db, include_all_fields: bool) -> fmt::Result {
|
fn fmt(&self, f: &mut fmt::Formatter<'_>, db: &Db) -> fmt::Result {
|
||||||
T::fmt(self, f, db, include_all_fields)
|
T::fmt(self, f, db)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<Db: ?Sized, T> DebugWithDb<Db> for Vec<T>
|
impl<Db: ?Sized, T> DebugWithDb<Db> for Vec<T>
|
||||||
where
|
where
|
||||||
T: DebugWithDb<Db>,
|
T: DebugWithDb<Db>,
|
||||||
|
Db: AsSalsaDatabase,
|
||||||
{
|
{
|
||||||
fn fmt(&self, f: &mut fmt::Formatter<'_>, db: &Db, include_all_fields: bool) -> fmt::Result {
|
fn fmt(&self, f: &mut fmt::Formatter<'_>, db: &Db) -> fmt::Result {
|
||||||
let elements = self.iter().map(|e| e.debug_with(db, include_all_fields));
|
let elements = self.iter().map(|e| e.debug(db));
|
||||||
f.debug_list().entries(elements).finish()
|
f.debug_list().entries(elements).finish()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -151,9 +172,10 @@ where
|
||||||
impl<Db: ?Sized, T> DebugWithDb<Db> for Option<T>
|
impl<Db: ?Sized, T> DebugWithDb<Db> for Option<T>
|
||||||
where
|
where
|
||||||
T: DebugWithDb<Db>,
|
T: DebugWithDb<Db>,
|
||||||
|
Db: AsSalsaDatabase,
|
||||||
{
|
{
|
||||||
fn fmt(&self, f: &mut fmt::Formatter<'_>, db: &Db, include_all_fields: bool) -> fmt::Result {
|
fn fmt(&self, f: &mut fmt::Formatter<'_>, db: &Db) -> fmt::Result {
|
||||||
let me = self.as_ref().map(|v| v.debug_with(db, include_all_fields));
|
let me = self.as_ref().map(|v| v.debug(db));
|
||||||
fmt::Debug::fmt(&me, f)
|
fmt::Debug::fmt(&me, f)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -162,14 +184,10 @@ impl<Db: ?Sized, K, V, S> DebugWithDb<Db> for HashMap<K, V, S>
|
||||||
where
|
where
|
||||||
K: DebugWithDb<Db>,
|
K: DebugWithDb<Db>,
|
||||||
V: DebugWithDb<Db>,
|
V: DebugWithDb<Db>,
|
||||||
|
Db: AsSalsaDatabase,
|
||||||
{
|
{
|
||||||
fn fmt(&self, f: &mut fmt::Formatter<'_>, db: &Db, include_all_fields: bool) -> fmt::Result {
|
fn fmt(&self, f: &mut fmt::Formatter<'_>, db: &Db) -> fmt::Result {
|
||||||
let elements = self.iter().map(|(k, v)| {
|
let elements = self.iter().map(|(k, v)| (k.debug(db), v.debug(db)));
|
||||||
(
|
|
||||||
k.debug_with(db, include_all_fields),
|
|
||||||
v.debug_with(db, include_all_fields),
|
|
||||||
)
|
|
||||||
});
|
|
||||||
f.debug_map().entries(elements).finish()
|
f.debug_map().entries(elements).finish()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -178,11 +196,12 @@ impl<Db: ?Sized, A, B> DebugWithDb<Db> for (A, B)
|
||||||
where
|
where
|
||||||
A: DebugWithDb<Db>,
|
A: DebugWithDb<Db>,
|
||||||
B: DebugWithDb<Db>,
|
B: DebugWithDb<Db>,
|
||||||
|
Db: AsSalsaDatabase,
|
||||||
{
|
{
|
||||||
fn fmt(&self, f: &mut fmt::Formatter<'_>, db: &Db, include_all_fields: bool) -> fmt::Result {
|
fn fmt(&self, f: &mut fmt::Formatter<'_>, db: &Db) -> fmt::Result {
|
||||||
f.debug_tuple("")
|
f.debug_tuple("")
|
||||||
.field(&self.0.debug_with(db, include_all_fields))
|
.field(&self.0.debug(db))
|
||||||
.field(&self.1.debug_with(db, include_all_fields))
|
.field(&self.1.debug(db))
|
||||||
.finish()
|
.finish()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -192,12 +211,13 @@ where
|
||||||
A: DebugWithDb<Db>,
|
A: DebugWithDb<Db>,
|
||||||
B: DebugWithDb<Db>,
|
B: DebugWithDb<Db>,
|
||||||
C: DebugWithDb<Db>,
|
C: DebugWithDb<Db>,
|
||||||
|
Db: AsSalsaDatabase,
|
||||||
{
|
{
|
||||||
fn fmt(&self, f: &mut fmt::Formatter<'_>, db: &Db, include_all_fields: bool) -> fmt::Result {
|
fn fmt(&self, f: &mut fmt::Formatter<'_>, db: &Db) -> fmt::Result {
|
||||||
f.debug_tuple("")
|
f.debug_tuple("")
|
||||||
.field(&self.0.debug_with(db, include_all_fields))
|
.field(&self.0.debug(db))
|
||||||
.field(&self.1.debug_with(db, include_all_fields))
|
.field(&self.1.debug(db))
|
||||||
.field(&self.2.debug_with(db, include_all_fields))
|
.field(&self.2.debug(db))
|
||||||
.finish()
|
.finish()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -205,9 +225,10 @@ where
|
||||||
impl<Db: ?Sized, V, S> DebugWithDb<Db> for HashSet<V, S>
|
impl<Db: ?Sized, V, S> DebugWithDb<Db> for HashSet<V, S>
|
||||||
where
|
where
|
||||||
V: DebugWithDb<Db>,
|
V: DebugWithDb<Db>,
|
||||||
|
Db: AsSalsaDatabase,
|
||||||
{
|
{
|
||||||
fn fmt(&self, f: &mut fmt::Formatter<'_>, db: &Db, include_all_fields: bool) -> fmt::Result {
|
fn fmt(&self, f: &mut fmt::Formatter<'_>, db: &Db) -> fmt::Result {
|
||||||
let elements = self.iter().map(|e| e.debug_with(db, include_all_fields));
|
let elements = self.iter().map(|e| e.debug(db));
|
||||||
f.debug_list().entries(elements).finish()
|
f.debug_list().entries(elements).finish()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -217,27 +238,27 @@ where
|
||||||
/// That's the "has impl" trick (https://github.com/nvzqz/impls#how-it-works)
|
/// That's the "has impl" trick (https://github.com/nvzqz/impls#how-it-works)
|
||||||
#[doc(hidden)]
|
#[doc(hidden)]
|
||||||
pub mod helper {
|
pub mod helper {
|
||||||
use super::{DebugWith, DebugWithDb};
|
use super::{AsSalsaDatabase, DebugWith, DebugWithDb};
|
||||||
use std::{fmt, marker::PhantomData};
|
use std::{fmt, marker::PhantomData};
|
||||||
|
|
||||||
pub trait Fallback<T: fmt::Debug, Db: ?Sized> {
|
pub trait Fallback<T: fmt::Debug, Db: ?Sized> {
|
||||||
fn salsa_debug<'a>(a: &'a T, _db: &Db, _include_all_fields: bool) -> &'a dyn fmt::Debug {
|
fn salsa_debug<'a>(a: &'a T, _db: &Db) -> &'a dyn fmt::Debug {
|
||||||
a
|
a
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl<Everything, Db: ?Sized, T: fmt::Debug> Fallback<T, Db> for Everything {}
|
||||||
|
|
||||||
pub struct SalsaDebug<T, Db: ?Sized>(PhantomData<T>, PhantomData<Db>);
|
pub struct SalsaDebug<T, Db: ?Sized>(PhantomData<T>, PhantomData<Db>);
|
||||||
|
|
||||||
impl<T: DebugWithDb<Db>, Db: ?Sized> SalsaDebug<T, Db> {
|
impl<T, Db: ?Sized> SalsaDebug<T, Db>
|
||||||
|
where
|
||||||
|
T: DebugWithDb<Db>,
|
||||||
|
Db: AsSalsaDatabase,
|
||||||
|
{
|
||||||
#[allow(dead_code)]
|
#[allow(dead_code)]
|
||||||
pub fn salsa_debug<'a, 'b: 'a>(
|
pub fn salsa_debug<'a, 'b: 'a>(a: &'a T, db: &'b Db) -> DebugWith<'a, Db> {
|
||||||
a: &'a T,
|
a.debug(db)
|
||||||
db: &'b Db,
|
|
||||||
include_all_fields: bool,
|
|
||||||
) -> DebugWith<'a, Db> {
|
|
||||||
a.debug_with(db, include_all_fields)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<Everything, Db: ?Sized, T: fmt::Debug> Fallback<T, Db> for Everything {}
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -28,15 +28,10 @@ impl<Db> DebugWithDb<Db> for Event
|
||||||
where
|
where
|
||||||
Db: ?Sized + Database,
|
Db: ?Sized + Database,
|
||||||
{
|
{
|
||||||
fn fmt(
|
fn fmt(&self, f: &mut std::fmt::Formatter<'_>, db: &Db) -> std::fmt::Result {
|
||||||
&self,
|
|
||||||
f: &mut std::fmt::Formatter<'_>,
|
|
||||||
db: &Db,
|
|
||||||
include_all_fields: bool,
|
|
||||||
) -> std::fmt::Result {
|
|
||||||
f.debug_struct("Event")
|
f.debug_struct("Event")
|
||||||
.field("runtime_id", &self.runtime_id)
|
.field("runtime_id", &self.runtime_id)
|
||||||
.field("kind", &self.kind.debug_with(db, include_all_fields))
|
.field("kind", &self.kind.debug(db))
|
||||||
.finish()
|
.finish()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -154,19 +149,11 @@ impl<Db> DebugWithDb<Db> for EventKind
|
||||||
where
|
where
|
||||||
Db: ?Sized + Database,
|
Db: ?Sized + Database,
|
||||||
{
|
{
|
||||||
fn fmt(
|
fn fmt(&self, fmt: &mut std::fmt::Formatter<'_>, db: &Db) -> std::fmt::Result {
|
||||||
&self,
|
|
||||||
fmt: &mut std::fmt::Formatter<'_>,
|
|
||||||
db: &Db,
|
|
||||||
include_all_fields: bool,
|
|
||||||
) -> std::fmt::Result {
|
|
||||||
match self {
|
match self {
|
||||||
EventKind::DidValidateMemoizedValue { database_key } => fmt
|
EventKind::DidValidateMemoizedValue { database_key } => fmt
|
||||||
.debug_struct("DidValidateMemoizedValue")
|
.debug_struct("DidValidateMemoizedValue")
|
||||||
.field(
|
.field("database_key", &database_key.debug(db))
|
||||||
"database_key",
|
|
||||||
&database_key.debug_with(db, include_all_fields),
|
|
||||||
)
|
|
||||||
.finish(),
|
.finish(),
|
||||||
EventKind::WillBlockOn {
|
EventKind::WillBlockOn {
|
||||||
other_runtime_id,
|
other_runtime_id,
|
||||||
|
@ -174,17 +161,11 @@ where
|
||||||
} => fmt
|
} => fmt
|
||||||
.debug_struct("WillBlockOn")
|
.debug_struct("WillBlockOn")
|
||||||
.field("other_runtime_id", other_runtime_id)
|
.field("other_runtime_id", other_runtime_id)
|
||||||
.field(
|
.field("database_key", &database_key.debug(db))
|
||||||
"database_key",
|
|
||||||
&database_key.debug_with(db, include_all_fields),
|
|
||||||
)
|
|
||||||
.finish(),
|
.finish(),
|
||||||
EventKind::WillExecute { database_key } => fmt
|
EventKind::WillExecute { database_key } => fmt
|
||||||
.debug_struct("WillExecute")
|
.debug_struct("WillExecute")
|
||||||
.field(
|
.field("database_key", &database_key.debug(db))
|
||||||
"database_key",
|
|
||||||
&database_key.debug_with(db, include_all_fields),
|
|
||||||
)
|
|
||||||
.finish(),
|
.finish(),
|
||||||
EventKind::WillCheckCancellation => fmt.debug_struct("WillCheckCancellation").finish(),
|
EventKind::WillCheckCancellation => fmt.debug_struct("WillCheckCancellation").finish(),
|
||||||
EventKind::WillDiscardStaleOutput {
|
EventKind::WillDiscardStaleOutput {
|
||||||
|
@ -192,29 +173,20 @@ where
|
||||||
output_key,
|
output_key,
|
||||||
} => fmt
|
} => fmt
|
||||||
.debug_struct("WillDiscardStaleOutput")
|
.debug_struct("WillDiscardStaleOutput")
|
||||||
.field(
|
.field("execute_key", &execute_key.debug(db))
|
||||||
"execute_key",
|
.field("output_key", &output_key.debug(db))
|
||||||
&execute_key.debug_with(db, include_all_fields),
|
|
||||||
)
|
|
||||||
.field("output_key", &output_key.debug_with(db, include_all_fields))
|
|
||||||
.finish(),
|
.finish(),
|
||||||
EventKind::DidDiscard { key } => fmt
|
EventKind::DidDiscard { key } => fmt
|
||||||
.debug_struct("DidDiscard")
|
.debug_struct("DidDiscard")
|
||||||
.field("key", &key.debug_with(db, include_all_fields))
|
.field("key", &key.debug(db))
|
||||||
.finish(),
|
.finish(),
|
||||||
EventKind::DidDiscardAccumulated {
|
EventKind::DidDiscardAccumulated {
|
||||||
executor_key,
|
executor_key,
|
||||||
accumulator,
|
accumulator,
|
||||||
} => fmt
|
} => fmt
|
||||||
.debug_struct("DidDiscardAccumulated")
|
.debug_struct("DidDiscardAccumulated")
|
||||||
.field(
|
.field("executor_key", &executor_key.debug(db))
|
||||||
"executor_key",
|
.field("accumulator", &accumulator.debug(db))
|
||||||
&executor_key.debug_with(db, include_all_fields),
|
|
||||||
)
|
|
||||||
.field(
|
|
||||||
"accumulator",
|
|
||||||
&accumulator.debug_with(db, include_all_fields),
|
|
||||||
)
|
|
||||||
.finish(),
|
.finish(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -38,12 +38,7 @@ impl<Db> crate::debug::DebugWithDb<Db> for DependencyIndex
|
||||||
where
|
where
|
||||||
Db: ?Sized + Database,
|
Db: ?Sized + Database,
|
||||||
{
|
{
|
||||||
fn fmt(
|
fn fmt(&self, f: &mut std::fmt::Formatter<'_>, db: &Db) -> std::fmt::Result {
|
||||||
&self,
|
|
||||||
f: &mut std::fmt::Formatter<'_>,
|
|
||||||
db: &Db,
|
|
||||||
_include_all_fields: bool,
|
|
||||||
) -> std::fmt::Result {
|
|
||||||
db.fmt_index(*self, f)
|
db.fmt_index(*self, f)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -73,14 +68,9 @@ impl<Db> crate::debug::DebugWithDb<Db> for DatabaseKeyIndex
|
||||||
where
|
where
|
||||||
Db: ?Sized + Database,
|
Db: ?Sized + Database,
|
||||||
{
|
{
|
||||||
fn fmt(
|
fn fmt(&self, f: &mut std::fmt::Formatter<'_>, db: &Db) -> std::fmt::Result {
|
||||||
&self,
|
|
||||||
f: &mut std::fmt::Formatter<'_>,
|
|
||||||
db: &Db,
|
|
||||||
include_all_fields: bool,
|
|
||||||
) -> std::fmt::Result {
|
|
||||||
let i: DependencyIndex = (*self).into();
|
let i: DependencyIndex = (*self).into();
|
||||||
DebugWithDb::fmt(&i, f, db, include_all_fields)
|
DebugWithDb::fmt(&i, f, db)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -104,6 +104,22 @@ impl Runtime {
|
||||||
self.shared_state.empty_dependencies.clone()
|
self.shared_state.empty_dependencies.clone()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Executes `op` but ignores its effect on
|
||||||
|
/// the query dependencies; intended for use
|
||||||
|
/// by `DebugWithDb` only.
|
||||||
|
///
|
||||||
|
/// # Danger: intended for debugging only
|
||||||
|
///
|
||||||
|
/// This operation is intended for **debugging only**.
|
||||||
|
/// Misuse will cause Salsa to give incorrect results.
|
||||||
|
/// The expectation is that the type `R` produced will be
|
||||||
|
/// logged or printed out. **The type `R` that is produced
|
||||||
|
/// should not affect the result or other outputs
|
||||||
|
/// (such as accumulators) from the current Salsa query.**
|
||||||
|
pub fn debug_probe<R>(&self, op: impl FnOnce() -> R) -> R {
|
||||||
|
self.local_state.debug_probe(op)
|
||||||
|
}
|
||||||
|
|
||||||
pub fn snapshot(&self) -> Self {
|
pub fn snapshot(&self) -> Self {
|
||||||
if self.local_state.query_in_progress() {
|
if self.local_state.query_in_progress() {
|
||||||
panic!("it is not legal to `snapshot` during a query (see salsa-rs/salsa#80)");
|
panic!("it is not legal to `snapshot` during a query (see salsa-rs/salsa#80)");
|
||||||
|
|
|
@ -40,6 +40,26 @@ pub(super) struct ActiveQuery {
|
||||||
pub(super) disambiguator_map: FxIndexMap<u64, Disambiguator>,
|
pub(super) disambiguator_map: FxIndexMap<u64, Disambiguator>,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub(super) struct SavedQueryState {
|
||||||
|
database_key_index: DatabaseKeyIndex,
|
||||||
|
durability: Durability,
|
||||||
|
changed_at: Revision,
|
||||||
|
input_outputs_len: usize,
|
||||||
|
untracked_read: bool,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl SavedQueryState {
|
||||||
|
fn new(query: &ActiveQuery) -> Self {
|
||||||
|
Self {
|
||||||
|
database_key_index: query.database_key_index,
|
||||||
|
durability: query.durability,
|
||||||
|
changed_at: query.changed_at,
|
||||||
|
input_outputs_len: query.input_outputs.len(),
|
||||||
|
untracked_read: query.untracked_read,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
impl ActiveQuery {
|
impl ActiveQuery {
|
||||||
pub(super) fn new(database_key_index: DatabaseKeyIndex) -> Self {
|
pub(super) fn new(database_key_index: DatabaseKeyIndex) -> Self {
|
||||||
ActiveQuery {
|
ActiveQuery {
|
||||||
|
@ -53,6 +73,26 @@ impl ActiveQuery {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub(super) fn save_query_state(&self) -> SavedQueryState {
|
||||||
|
SavedQueryState::new(self)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub(super) fn restore_query_state(&mut self, state: SavedQueryState) {
|
||||||
|
assert_eq!(self.database_key_index, state.database_key_index);
|
||||||
|
|
||||||
|
assert!(self.durability <= state.durability);
|
||||||
|
self.durability = state.durability;
|
||||||
|
|
||||||
|
assert!(self.changed_at >= state.changed_at);
|
||||||
|
self.changed_at = state.changed_at;
|
||||||
|
|
||||||
|
assert!(self.input_outputs.len() >= state.input_outputs_len);
|
||||||
|
self.input_outputs.truncate(state.input_outputs_len);
|
||||||
|
|
||||||
|
assert!(self.untracked_read >= state.untracked_read);
|
||||||
|
self.untracked_read = state.untracked_read;
|
||||||
|
}
|
||||||
|
|
||||||
pub(super) fn add_read(
|
pub(super) fn add_read(
|
||||||
&mut self,
|
&mut self,
|
||||||
input: DependencyIndex,
|
input: DependencyIndex,
|
||||||
|
|
|
@ -174,6 +174,25 @@ impl LocalState {
|
||||||
self.with_query_stack(|stack| !stack.is_empty())
|
self.with_query_stack(|stack| !stack.is_empty())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Dangerous operation: executes `op` but ignores its effect on
|
||||||
|
/// the query dependencies. Useful for debugging statements, but
|
||||||
|
/// otherwise not to be toyed with!
|
||||||
|
pub(super) fn debug_probe<R>(&self, op: impl FnOnce() -> R) -> R {
|
||||||
|
let saved_state: Option<_> =
|
||||||
|
self.with_query_stack(|stack| Some(stack.last()?.save_query_state()));
|
||||||
|
|
||||||
|
let result = op();
|
||||||
|
|
||||||
|
if let Some(saved_state) = saved_state {
|
||||||
|
self.with_query_stack(|stack| {
|
||||||
|
let active_query = stack.last_mut().expect("query stack not empty");
|
||||||
|
active_query.restore_query_state(saved_state);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
result
|
||||||
|
}
|
||||||
|
|
||||||
/// Returns the index of the active query along with its *current* durability/changed-at
|
/// Returns the index of the active query along with its *current* durability/changed-at
|
||||||
/// information. As the query continues to execute, naturally, that information may change.
|
/// information. As the query continues to execute, naturally, that information may change.
|
||||||
pub(super) fn active_query(&self) -> Option<(DatabaseKeyIndex, StampedValue<()>)> {
|
pub(super) fn active_query(&self) -> Option<(DatabaseKeyIndex, StampedValue<()>)> {
|
||||||
|
|
|
@ -367,7 +367,7 @@ fn parse_string(source_text: &str) -> String {
|
||||||
let accumulated = parse_statements::accumulated::<Diagnostics>(&db, source_program);
|
let accumulated = parse_statements::accumulated::<Diagnostics>(&db, source_program);
|
||||||
|
|
||||||
// Format the result as a string and return it
|
// Format the result as a string and return it
|
||||||
format!("{:#?}", (statements.debug_all(&db), accumulated))
|
format!("{:#?}", (statements.debug(&db), accumulated))
|
||||||
}
|
}
|
||||||
// ANCHOR_END: parse_string
|
// ANCHOR_END: parse_string
|
||||||
|
|
||||||
|
|
|
@ -4,7 +4,13 @@ use expect_test::expect;
|
||||||
use salsa::DebugWithDb;
|
use salsa::DebugWithDb;
|
||||||
|
|
||||||
#[salsa::jar(db = Db)]
|
#[salsa::jar(db = Db)]
|
||||||
struct Jar(MyInput, ComplexStruct);
|
struct Jar(
|
||||||
|
MyInput,
|
||||||
|
ComplexStruct,
|
||||||
|
leak_debug_string,
|
||||||
|
DerivedCustom,
|
||||||
|
leak_derived_custom,
|
||||||
|
);
|
||||||
|
|
||||||
trait Db: salsa::DbWithJar<Jar> {}
|
trait Db: salsa::DbWithJar<Jar> {}
|
||||||
|
|
||||||
|
@ -44,15 +50,75 @@ fn input() {
|
||||||
};
|
};
|
||||||
let complex_struct = ComplexStruct::new(&db, input, not_salsa);
|
let complex_struct = ComplexStruct::new(&db, input, not_salsa);
|
||||||
|
|
||||||
// default debug only includes identity fields
|
// debug includes all fields
|
||||||
let actual = format!("{:?}", complex_struct.debug(&db));
|
let actual = format!("{:?}", complex_struct.debug(&db));
|
||||||
let expected = expect!["ComplexStruct { [salsa id]: 0 }"];
|
|
||||||
expected.assert_eq(&actual);
|
|
||||||
|
|
||||||
// all fields
|
|
||||||
let actual = format!("{:?}", complex_struct.debug_all(&db));
|
|
||||||
let expected = expect![[
|
let expected = expect![[
|
||||||
r#"ComplexStruct { [salsa id]: 0, my_input: MyInput { [salsa id]: 0, field: 22 }, not_salsa: NotSalsa { field: "it's salsa time" } }"#
|
r#"ComplexStruct { [salsa id]: 0, my_input: MyInput { [salsa id]: 0, field: 22 }, not_salsa: NotSalsa { field: "it's salsa time" } }"#
|
||||||
]];
|
]];
|
||||||
expected.assert_eq(&actual);
|
expected.assert_eq(&actual);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[salsa::tracked]
|
||||||
|
fn leak_debug_string(db: &dyn Db, input: MyInput) -> String {
|
||||||
|
format!("{:?}", input.debug(db))
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Test that field reads that occur as part of `Debug` are not tracked.
|
||||||
|
/// Intentionally leaks the debug string.
|
||||||
|
/// Don't try this at home, kids.
|
||||||
|
#[test]
|
||||||
|
fn untracked_dependencies() {
|
||||||
|
let mut db = Database::default();
|
||||||
|
|
||||||
|
let input = MyInput::new(&db, 22);
|
||||||
|
|
||||||
|
let s = leak_debug_string(&db, input);
|
||||||
|
expect![[r#"
|
||||||
|
"MyInput { [salsa id]: 0, field: 22 }"
|
||||||
|
"#]]
|
||||||
|
.assert_debug_eq(&s);
|
||||||
|
|
||||||
|
input.set_field(&mut db).to(23);
|
||||||
|
|
||||||
|
// check that we reuse the cached result for debug string
|
||||||
|
// even though the dependency changed.
|
||||||
|
let s = leak_debug_string(&db, input);
|
||||||
|
assert!(s.contains(", field: 22 }"));
|
||||||
|
}
|
||||||
|
|
||||||
|
#[salsa::tracked]
|
||||||
|
#[customize(DebugWithDb)]
|
||||||
|
struct DerivedCustom {
|
||||||
|
my_input: MyInput,
|
||||||
|
value: u32,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl DebugWithDb<dyn Db + '_> for DerivedCustom {
|
||||||
|
fn fmt(&self, f: &mut std::fmt::Formatter<'_>, db: &dyn Db) -> std::fmt::Result {
|
||||||
|
write!(
|
||||||
|
f,
|
||||||
|
"{:?} / {:?}",
|
||||||
|
self.my_input(db).debug(db),
|
||||||
|
self.value(db)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[salsa::tracked]
|
||||||
|
fn leak_derived_custom(db: &dyn Db, input: MyInput, value: u32) -> String {
|
||||||
|
let c = DerivedCustom::new(db, input, value);
|
||||||
|
format!("{:?}", c.debug(db))
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn custom_debug_impl() {
|
||||||
|
let db = Database::default();
|
||||||
|
|
||||||
|
let input = MyInput::new(&db, 22);
|
||||||
|
|
||||||
|
let s = leak_derived_custom(&db, input, 23);
|
||||||
|
expect![[r#"
|
||||||
|
"MyInput { [salsa id]: 0, field: 22 } / 23"
|
||||||
|
"#]]
|
||||||
|
.assert_debug_eq(&s);
|
||||||
|
}
|
||||||
|
|
|
@ -38,7 +38,7 @@ fn test_debug() {
|
||||||
let input = MyInput::new(&mut db, 50, 10, Field {});
|
let input = MyInput::new(&mut db, 50, 10, Field {});
|
||||||
|
|
||||||
let actual = format!("{:?}", input.debug(&db));
|
let actual = format!("{:?}", input.debug(&db));
|
||||||
let expected = expect![[r#"MyInput { [salsa id]: 0, id_one: 50, id_two: 10 }"#]];
|
let expected = expect!["MyInput { [salsa id]: 0, id_one: 50, id_two: 10, field: Field }"];
|
||||||
expected.assert_eq(&actual);
|
expected.assert_eq(&actual);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -67,6 +67,6 @@ fn debug() {
|
||||||
let db = Database::default();
|
let db = Database::default();
|
||||||
let input = MyInput::new(&db, 3, 4);
|
let input = MyInput::new(&db, 3, 4);
|
||||||
let actual = format!("{:?}", input.debug(&db));
|
let actual = format!("{:?}", input.debug(&db));
|
||||||
let expected = expect![[r#"MyInput { [salsa id]: 0, id_field: 4 }"#]];
|
let expected = expect!["MyInput { [salsa id]: 0, field: 3, id_field: 4 }"];
|
||||||
expected.assert_eq(&actual);
|
expected.assert_eq(&actual);
|
||||||
}
|
}
|
||||||
|
|
|
@ -55,7 +55,7 @@ fn execute() {
|
||||||
assert_eq!(tracked_fn(&db, input), 2222);
|
assert_eq!(tracked_fn(&db, input), 2222);
|
||||||
db.assert_logs(expect![[r#"
|
db.assert_logs(expect![[r#"
|
||||||
[
|
[
|
||||||
"tracked_fn(MyInput { [salsa id]: 0 })",
|
"tracked_fn(MyInput { [salsa id]: 0, field: 22 })",
|
||||||
]"#]]);
|
]"#]]);
|
||||||
|
|
||||||
// A "synthetic write" causes the system to act *as though* some
|
// A "synthetic write" causes the system to act *as though* some
|
||||||
|
|
|
@ -7,12 +7,7 @@ struct Jar(TokenTree);
|
||||||
enum Token {}
|
enum Token {}
|
||||||
|
|
||||||
impl salsa::DebugWithDb<dyn Db + '_> for Token {
|
impl salsa::DebugWithDb<dyn Db + '_> for Token {
|
||||||
fn fmt(
|
fn fmt(&self, _f: &mut std::fmt::Formatter<'_>, _db: &dyn Db) -> std::fmt::Result {
|
||||||
&self,
|
|
||||||
_f: &mut std::fmt::Formatter<'_>,
|
|
||||||
_db: &dyn Db,
|
|
||||||
_include_all_fields: bool,
|
|
||||||
) -> std::fmt::Result {
|
|
||||||
unreachable!()
|
unreachable!()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in a new issue