Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feature: add #[salsa::interned_sans_lifetime] macro #632

Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions components/salsa-macro-rules/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ mod maybe_default;
mod setup_accumulator_impl;
mod setup_input_struct;
mod setup_interned_struct;
mod setup_interned_struct_sans_lifetime;
mod setup_method_body;
mod setup_tracked_fn;
mod setup_tracked_struct;
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,226 @@
/// Macro for setting up a function that must intern its arguments, without any lifetimes.
#[macro_export]
macro_rules! setup_interned_struct_sans_lifetime {
(
// Attributes on the struct
attrs: [$(#[$attr:meta]),*],

// Visibility of the struct
vis: $vis:vis,

// Name of the struct
Struct: $Struct:ident,

// Name of the struct data. This is a parameter because `std::concat_idents`
// is unstable and taking an addition dependency is unnecessary.
StructData: $StructDataIdent:ident,

// Name of the `'db` lifetime that the user gave
db_lt: $db_lt:lifetime,

// the salsa ID
id: $Id:path,

// Name user gave for `new`
new_fn: $new_fn:ident,

// A series of option tuples; see `setup_tracked_struct` macro
field_options: [$($field_option:tt),*],

// Field names
field_ids: [$($field_id:ident),*],

// Names for field setter methods (typically `set_foo`)
field_getters: [$($field_getter_vis:vis $field_getter_id:ident),*],

// Field types
field_tys: [$($field_ty:ty),*],

// Indices for each field from 0..N -- must be unsuffixed (e.g., `0`, `1`).
field_indices: [$($field_index:tt),*],

// Indexed types for each field (T0, T1, ...)
field_indexed_tys: [$($indexed_ty:ident),*],

// Number of fields
num_fields: $N:literal,

// If true, generate a debug impl.
generate_debug_impl: $generate_debug_impl:tt,

// Annoyingly macro-rules hygiene does not extend to items defined in the macro.
// We have the procedural macro generate names for those items that are
// not used elsewhere in the user's code.
unused_names: [
$zalsa:ident,
$zalsa_struct:ident,
$Configuration:ident,
$CACHE:ident,
$Db:ident,
]
) => {
$(#[$attr])*
#[derive(Copy, Clone, PartialEq, PartialOrd, Eq, Ord, Hash)]
$vis struct $Struct(
$Id,
std::marker::PhantomData < &'static salsa::plumbing::interned::Value < $Struct > >
);

type $StructDataIdent<$db_lt> = ($($field_ty,)*);

impl salsa::plumbing::interned::Configuration for $Struct {
const DEBUG_NAME: &'static str = stringify!($Struct);
type Data<'a> = $StructDataIdent<'a>;
type Struct<'a> = $Struct;
fn struct_from_id<'db>(id: salsa::Id) -> Self::Struct<'db> {
use salsa::plumbing::FromId;
$Struct(<$Id>::from_id(id), std::marker::PhantomData)
}
fn deref_struct(s: Self::Struct<'_>) -> salsa::Id {
use salsa::plumbing::AsId;
s.0.as_id()
}
}

const _: () = {
use salsa::plumbing as $zalsa;
use $zalsa::interned as $zalsa_struct;

type $Configuration = $Struct;

/// Key to use during hash lookups. Each field is some type that implements `Lookup<T>`
/// for the owned type. This permits interning with an `&str` when a `String` is required and so forth.
#[derive(Hash)]
struct StructKey<$db_lt, $($indexed_ty),*>(
$($indexed_ty,)*
std::marker::PhantomData<&$db_lt ()>,
);

impl<$db_lt, $($indexed_ty,)*> $zalsa::interned::HashEqLike<StructKey<$db_lt, $($indexed_ty),*>>
for $StructDataIdent<$db_lt>
where
$($field_ty: $zalsa::interned::HashEqLike<$indexed_ty>),*
{
fn hash<H: std::hash::Hasher>(&self, h: &mut H) {
$($zalsa::interned::HashEqLike::<$indexed_ty>::hash(&self.$field_index, &mut *h);)*
}

fn eq(&self, data: &StructKey<$db_lt, $($indexed_ty),*>) -> bool {
($($zalsa::interned::HashEqLike::<$indexed_ty>::eq(&self.$field_index, &data.$field_index) && )* true)
}
}

impl<$db_lt, $($indexed_ty: $zalsa::interned::Lookup<$field_ty>),*> $zalsa::interned::Lookup<$StructDataIdent<$db_lt>>
for StructKey<$db_lt, $($indexed_ty),*> {

#[allow(unused_unit)]
fn into_owned(self) -> $StructDataIdent<$db_lt> {
($($zalsa::interned::Lookup::into_owned(self.$field_index),)*)
}
}

impl $Configuration {
pub fn ingredient<Db>(db: &Db) -> &$zalsa_struct::IngredientImpl<Self>
where
Db: ?Sized + $zalsa::Database,
{
static CACHE: $zalsa::IngredientCache<$zalsa_struct::IngredientImpl<$Configuration>> =
$zalsa::IngredientCache::new();
CACHE.get_or_create(db.as_dyn_database(), || {
db.zalsa().add_or_lookup_jar_by_type(&<$zalsa_struct::JarImpl<$Configuration>>::default())
})
}
}

impl $zalsa::AsId for $Struct {
fn as_id(&self) -> salsa::Id {
self.0.as_id()
}
}

impl $zalsa::FromId for $Struct {
fn from_id(id: salsa::Id) -> Self {
Self(<$Id>::from_id(id), std::marker::PhantomData)
}
}

unsafe impl Send for $Struct {}

unsafe impl Sync for $Struct {}

$zalsa::macro_if! { $generate_debug_impl =>
impl std::fmt::Debug for $Struct {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
Self::default_debug_fmt(*self, f)
}
}
}

impl $zalsa::SalsaStructInDb for $Struct {
fn lookup_ingredient_index(aux: &dyn $zalsa::JarAux) -> core::option::Option<$zalsa::IngredientIndex> {
aux.lookup_jar_by_type(&<$zalsa_struct::JarImpl<$Configuration>>::default())
}
}

unsafe impl $zalsa::Update for $Struct {
unsafe fn maybe_update(old_pointer: *mut Self, new_value: Self) -> bool {
if unsafe { *old_pointer } != new_value {
unsafe { *old_pointer = new_value };
true
} else {
false
}
}
}

impl<$db_lt> $Struct {
pub fn $new_fn<$Db, $($indexed_ty: $zalsa::interned::Lookup<$field_ty> + std::hash::Hash,)*>(db: &$db_lt $Db, $($field_id: $indexed_ty),*) -> Self
where
// FIXME(rust-lang/rust#65991): The `db` argument *should* have the type `dyn Database`
$Db: ?Sized + salsa::Database,
$(
$field_ty: $zalsa::interned::HashEqLike<$indexed_ty>,
)*
{
let current_revision = $zalsa::current_revision(db);
$Configuration::ingredient(db).intern(
db.as_dyn_database(),
StructKey::<$db_lt>($($field_id,)* std::marker::PhantomData::default()),
|_, data| ($($zalsa::interned::Lookup::into_owned(data.$field_index),)*)
)
}

$(
$field_getter_vis fn $field_getter_id<$Db>(self, db: &'db $Db) -> $zalsa::maybe_cloned_ty!($field_option, 'db, $field_ty)
where
// FIXME(rust-lang/rust#65991): The `db` argument *should* have the type `dyn Database`
$Db: ?Sized + $zalsa::Database,
{
let fields = $Configuration::ingredient(db).fields(db.as_dyn_database(), self);
$zalsa::maybe_clone!(
$field_option,
$field_ty,
&fields.$field_index,
)
}
)*

/// Default debug formatting for this struct (may be useful if you define your own `Debug` impl)
pub fn default_debug_fmt(this: Self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
$zalsa::with_attached_database(|db| {
let fields = $Configuration::ingredient(db).fields(db.as_dyn_database(), this);
let mut f = f.debug_struct(stringify!($Struct));
$(
let f = f.field(stringify!($field_id), &fields.$field_index);
)*
f.finish()
}).unwrap_or_else(|| {
f.debug_tuple(stringify!($Struct))
.field(&$zalsa::AsId::as_id(&this))
.finish()
})
}
}
};
};
}
1 change: 1 addition & 0 deletions components/salsa-macros/src/accumulator.rs
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,7 @@ impl AllowedOptions for Accumulator {
const RECOVERY_FN: bool = false;
const LRU: bool = false;
const CONSTRUCTOR_NAME: bool = false;
const ID: bool = true;
}

struct StructMacro {
Expand Down
2 changes: 2 additions & 0 deletions components/salsa-macros/src/input.rs
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,8 @@ impl crate::options::AllowedOptions for InputStruct {
const LRU: bool = false;

const CONSTRUCTOR_NAME: bool = true;

const ID: bool = true;
}

impl SalsaStructAllowedOptions for InputStruct {
Expand Down
2 changes: 2 additions & 0 deletions components/salsa-macros/src/interned.rs
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,8 @@ impl crate::options::AllowedOptions for InternedStruct {
const LRU: bool = false;

const CONSTRUCTOR_NAME: bool = true;

const ID: bool = true;
}

impl SalsaStructAllowedOptions for InternedStruct {
Expand Down
138 changes: 138 additions & 0 deletions components/salsa-macros/src/interned_sans_lifetime.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,138 @@
use crate::{
db_lifetime,
hygiene::Hygiene,
options::Options,
salsa_struct::{SalsaStruct, SalsaStructAllowedOptions},
token_stream_with_error,
};
use proc_macro2::TokenStream;

/// For an entity struct `Foo` with fields `f1: T1, ..., fN: TN`, we generate...
///
/// * the "id struct" `struct Foo(salsa::Id)`
/// * the entity ingredient, which maps the id fields to the `Id`
/// * for each value field, a function ingredient
pub(crate) fn interned_sans_lifetime(
args: proc_macro::TokenStream,
input: proc_macro::TokenStream,
) -> proc_macro::TokenStream {
let args = syn::parse_macro_input!(args as InternedArgs);
let hygiene = Hygiene::from1(&input);
let struct_item = parse_macro_input!(input as syn::ItemStruct);
let m = Macro {
hygiene,
args,
struct_item,
};
match m.try_macro() {
Ok(v) => v.into(),
Err(e) => token_stream_with_error(input, e),
}
}

type InternedArgs = Options<InternedStruct>;

#[derive(Debug)]
struct InternedStruct;

impl crate::options::AllowedOptions for InternedStruct {
const RETURN_REF: bool = false;

const SPECIFY: bool = false;

const NO_EQ: bool = false;

const NO_DEBUG: bool = true;

const NO_CLONE: bool = false;

const SINGLETON: bool = true;

const DATA: bool = true;

const DB: bool = false;

const RECOVERY_FN: bool = false;

const LRU: bool = false;

const CONSTRUCTOR_NAME: bool = true;

const ID: bool = true;
}

impl SalsaStructAllowedOptions for InternedStruct {
const KIND: &'static str = "interned";

const ALLOW_ID: bool = false;

const HAS_LIFETIME: bool = false;

const ALLOW_DEFAULT: bool = false;
}

struct Macro {
hygiene: Hygiene,
args: InternedArgs,
struct_item: syn::ItemStruct,
}

impl Macro {
#[allow(non_snake_case)]
fn try_macro(&self) -> syn::Result<TokenStream> {
let salsa_struct = SalsaStruct::new(&self.struct_item, &self.args)?;

let attrs = &self.struct_item.attrs;
let vis = &self.struct_item.vis;
let struct_ident = &self.struct_item.ident;
let struct_data_ident = format_ident!("{}Data", struct_ident);
let db_lt = db_lifetime::db_lifetime(&self.struct_item.generics);
let new_fn = salsa_struct.constructor_name();
let field_ids = salsa_struct.field_ids();
let field_indices = salsa_struct.field_indices();
let num_fields = salsa_struct.num_fields();
let field_vis = salsa_struct.field_vis();
let field_getter_ids = salsa_struct.field_getter_ids();
let field_options = salsa_struct.field_options();
let field_tys = salsa_struct.field_tys();
let field_indexed_tys = salsa_struct.field_indexed_tys();
let generate_debug_impl = salsa_struct.generate_debug_impl();
let id = salsa_struct.id();

let zalsa = self.hygiene.ident("zalsa");
let zalsa_struct = self.hygiene.ident("zalsa_struct");
let Configuration = self.hygiene.ident("Configuration");
let CACHE = self.hygiene.ident("CACHE");
let Db = self.hygiene.ident("Db");

Ok(crate::debug::dump_tokens(
struct_ident,
quote! {
salsa::plumbing::setup_interned_struct_sans_lifetime!(
attrs: [#(#attrs),*],
vis: #vis,
Struct: #struct_ident,
StructData: #struct_data_ident,
db_lt: #db_lt,
id: #id,
new_fn: #new_fn,
field_options: [#(#field_options),*],
field_ids: [#(#field_ids),*],
field_getters: [#(#field_vis #field_getter_ids),*],
field_tys: [#(#field_tys),*],
field_indices: [#(#field_indices),*],
field_indexed_tys: [#(#field_indexed_tys),*],
num_fields: #num_fields,
generate_debug_impl: #generate_debug_impl,
unused_names: [
#zalsa,
#zalsa_struct,
#Configuration,
#CACHE,
#Db,
]
);
},
))
}
}
Loading
Loading