Support singleton inputs

This commit is contained in:
OLUWAMUYIWA 2022-09-02 02:54:21 +01:00
parent 657b85682b
commit 565c53d4dd
13 changed files with 310 additions and 29 deletions

View file

@ -25,6 +25,8 @@ impl crate::options::AllowedOptions for Accumulator {
const NO_EQ: bool = false;
const SINGLETON: bool = false;
const JAR: bool = true;
const DATA: bool = false;
@ -38,6 +40,16 @@ impl crate::options::AllowedOptions for Accumulator {
const CONSTRUCTOR_NAME: bool = false;
}
impl crate::modes::AllowedModes for Accumulator {
const TRACKED: bool = false;
const INPUT: bool = false;
const INTERNED: bool = false;
const ACCUMULATOR: bool = true;
}
fn accumulator_contents(
args: &Args,
struct_item: &syn::ItemStruct,

View file

@ -1,6 +1,6 @@
use proc_macro2::{Literal, TokenStream};
use crate::modes::Mode;
use crate::salsa_struct::{SalsaField, SalsaStruct};
use proc_macro2::{Literal, TokenStream};
/// For an entity struct `Foo` with fields `f1: T1, ..., fN: TN`, we generate...
///
@ -11,16 +11,19 @@ pub(crate) fn input(
args: proc_macro::TokenStream,
input: proc_macro::TokenStream,
) -> proc_macro::TokenStream {
match SalsaStruct::new(args, input).and_then(|el| InputStruct(el).generate_input()) {
let mode = Mode {
..Default::default()
};
match SalsaStruct::new(args, input, mode).and_then(|el| InputStruct(el).generate_input()) {
Ok(s) => s.into(),
Err(err) => err.into_compile_error().into(),
}
}
struct InputStruct(SalsaStruct<Self>);
struct InputStruct(SalsaStruct<Self, Self>);
impl std::ops::Deref for InputStruct {
type Target = SalsaStruct<Self>;
type Target = SalsaStruct<Self, Self>;
fn deref(&self) -> &Self::Target {
&self.0
@ -33,6 +36,7 @@ impl crate::options::AllowedOptions for InputStruct {
const SPECIFY: bool = false;
const NO_EQ: bool = false;
const SINGLETON: bool = true;
const JAR: bool = true;
@ -47,6 +51,16 @@ impl crate::options::AllowedOptions for InputStruct {
const CONSTRUCTOR_NAME: bool = true;
}
impl crate::modes::AllowedModes for InputStruct {
const TRACKED: bool = false;
const INPUT: bool = true;
const INTERNED: bool = false;
const ACCUMULATOR: bool = false;
}
impl InputStruct {
fn generate_input(&self) -> syn::Result<TokenStream> {
self.validate_input()?;
@ -73,8 +87,16 @@ impl InputStruct {
}
fn validate_input(&self) -> syn::Result<()> {
// check for dissalowed fields
self.disallow_id_fields("input")?;
// check if an input struct labeled singleton truly has one field
if self.0.is_isingleton() && self.0.num_fields() != 1 {
return Err(syn::Error::new(
self.0.struct_span(),
format!("`Singleton` input mut have only one field"),
));
}
Ok(())
}
@ -127,8 +149,25 @@ impl InputStruct {
.collect();
let constructor_name = self.constructor_name();
let singleton = self.0.is_isingleton();
let constructor: syn::ImplItemMethod = if singleton {
parse_quote! {
pub fn #constructor_name(__db: &mut #db_dyn_ty, #(#field_names: #field_tys,)*) -> Self
{
let (__jar, __runtime) = <_ as salsa::storage::HasJar<#jar_ty>>::jar_mut(__db);
let __ingredients = <#jar_ty as salsa::storage::HasIngredientsFor< #ident >>::ingredient_mut(__jar);
let __id = __ingredients.#input_index.new_singleton_input(__runtime);
#(
__ingredients.#field_indices.store(__runtime, __id, #field_names, salsa::Durability::LOW);
)*
__id
}
}
} else {
parse_quote! {
impl #ident {
pub fn #constructor_name(__db: &mut #db_dyn_ty, #(#field_names: #field_tys,)*) -> Self
{
let (__jar, __runtime) = <_ as salsa::storage::HasJar<#jar_ty>>::jar_mut(__db);
@ -139,12 +178,54 @@ impl InputStruct {
)*
__id
}
}
};
if singleton {
let get: syn::ImplItemMethod = parse_quote! {
#[track_caller]
pub fn get(__db: &#db_dyn_ty) -> Self {
let (__jar, __runtime) = <_ as salsa::storage::HasJar<#jar_ty>>::jar(__db);
let __ingredients = <#jar_ty as salsa::storage::HasIngredientsFor< #ident >>::ingredient(__jar);
__ingredients.#input_index.get_singleton_input(__runtime).expect("singleton not yet initialized")
}
};
let try_get: syn::ImplItemMethod = parse_quote! {
#[track_caller]
pub fn try_get(__db: &#db_dyn_ty) -> Option<Self> {
let (__jar, __runtime) = <_ as salsa::storage::HasJar<#jar_ty>>::jar(__db);
let __ingredients = <#jar_ty as salsa::storage::HasIngredientsFor< #ident >>::ingredient(__jar);
__ingredients.#input_index.get_singleton_input(__runtime)
}
};
parse_quote! {
impl #ident {
#constructor
#get
#try_get
#(#field_getters)*
#(#field_setters)*
}
}
} else {
parse_quote! {
impl #ident {
#constructor
#(#field_getters)*
#(#field_setters)*
}
}
}
// }
}
/// Generate the `IngredientsFor` impl for this entity.

View file

@ -1,6 +1,6 @@
use proc_macro2::TokenStream;
use crate::modes::Mode;
use crate::salsa_struct::SalsaStruct;
use proc_macro2::TokenStream;
// #[salsa::interned(jar = Jar0, data = TyData0)]
// #[derive(Eq, PartialEq, Hash, Debug, Clone)]
@ -14,16 +14,20 @@ pub(crate) fn interned(
args: proc_macro::TokenStream,
input: proc_macro::TokenStream,
) -> proc_macro::TokenStream {
match SalsaStruct::new(args, input).and_then(|el| InternedStruct(el).generate_interned()) {
let mode = Mode {
..Default::default()
};
match SalsaStruct::new(args, input, mode).and_then(|el| InternedStruct(el).generate_interned())
{
Ok(s) => s.into(),
Err(err) => err.into_compile_error().into(),
}
}
struct InternedStruct(SalsaStruct<Self>);
struct InternedStruct(SalsaStruct<Self, Self>);
impl std::ops::Deref for InternedStruct {
type Target = SalsaStruct<Self>;
type Target = SalsaStruct<Self, Self>;
fn deref(&self) -> &Self::Target {
&self.0
@ -37,6 +41,8 @@ impl crate::options::AllowedOptions for InternedStruct {
const NO_EQ: bool = false;
const SINGLETON: bool = false;
const JAR: bool = true;
const DATA: bool = true;
@ -50,6 +56,16 @@ impl crate::options::AllowedOptions for InternedStruct {
const CONSTRUCTOR_NAME: bool = true;
}
impl crate::modes::AllowedModes for InternedStruct {
const TRACKED: bool = false;
const INPUT: bool = true;
const INTERNED: bool = false;
const ACCUMULATOR: bool = false;
}
impl InternedStruct {
fn generate_interned(&self) -> syn::Result<TokenStream> {
self.validate_interned()?;

View file

@ -34,6 +34,8 @@ impl crate::options::AllowedOptions for Jar {
const NO_EQ: bool = false;
const SINGLETON: bool = false;
const JAR: bool = false;
const DATA: bool = false;

View file

@ -36,6 +36,7 @@ mod db;
mod input;
mod interned;
mod jar;
mod modes;
mod options;
mod salsa_struct;
mod tracked;

View file

@ -0,0 +1,31 @@
use std::marker::PhantomData;
/// The four possible modes of Salsa structs
/// Salsa structs asre generic over AllowedModes.
pub(crate) trait AllowedModes {
const TRACKED: bool;
const INPUT: bool;
const INTERNED: bool;
const ACCUMULATOR: bool;
}
///
pub(crate) struct Mode<M: AllowedModes> {
pub(super) phantom: PhantomData<M>,
}
impl<M: AllowedModes> Default for Mode<M> {
fn default() -> Self {
Self {
phantom: Default::default(),
}
}
}
impl<M: AllowedModes> Mode<M> {
pub(crate) fn singleton_allowed(&self) -> bool {
M::INPUT
}
}

View file

@ -19,6 +19,10 @@ pub(crate) struct Options<A: AllowedOptions> {
/// If this is `Some`, the value is the `no_eq` identifier.
pub no_eq: Option<syn::Ident>,
/// The `singleton` option is used on input with only one field
/// It allows the creation of convenient methods
pub singleton: Option<syn::Ident>,
/// The `specify` option is used to signal that a tracked function can
/// have its value externally specified (at least some of the time).
///
@ -74,6 +78,7 @@ impl<A: AllowedOptions> Default for Options<A> {
constructor_name: Default::default(),
phantom: Default::default(),
lru: Default::default(),
singleton: Default::default(),
}
}
}
@ -83,6 +88,7 @@ pub(crate) trait AllowedOptions {
const RETURN_REF: bool;
const SPECIFY: bool;
const NO_EQ: bool;
const SINGLETON: bool;
const JAR: bool;
const DATA: bool;
const DB: bool;
@ -141,6 +147,20 @@ impl<A: AllowedOptions> syn::parse::Parse for Options<A> {
"`no_eq` option not allowed here",
));
}
} else if ident == "singleton" {
if A::SINGLETON {
if let Some(old) = std::mem::replace(&mut options.singleton, Some(ident)) {
return Err(syn::Error::new(
old.span(),
"option `singleton` provided twice",
));
}
} else {
return Err(syn::Error::new(
ident.span(),
"`singleton` option not allowed here",
));
}
} else if ident == "specify" {
if A::SPECIFY {
if let Some(old) = std::mem::replace(&mut options.specify, Some(ident)) {

View file

@ -25,41 +25,46 @@
//! * data method `impl Foo { fn data(&self, db: &dyn crate::Db) -> FooData { FooData { f: self.f(db), ... } } }`
//! * this could be optimized, particularly for interned fields
use crate::modes::Mode;
use crate::{
configuration,
modes::AllowedModes,
options::{AllowedOptions, Options},
};
use heck::ToUpperCamelCase;
use proc_macro2::{Ident, Literal, Span, TokenStream};
use syn::spanned::Spanned;
use crate::{
configuration,
options::{AllowedOptions, Options},
};
pub(crate) struct SalsaStruct<A: AllowedOptions> {
pub(crate) struct SalsaStruct<A: AllowedOptions, M: AllowedModes> {
args: Options<A>,
_mode: Mode<M>,
struct_item: syn::ItemStruct,
fields: Vec<SalsaField>,
}
const BANNED_FIELD_NAMES: &[&str] = &["from", "new"];
impl<A: AllowedOptions> SalsaStruct<A> {
impl<A: AllowedOptions, M: AllowedModes> SalsaStruct<A, M> {
pub(crate) fn new(
args: proc_macro::TokenStream,
input: proc_macro::TokenStream,
mode: Mode<M>,
) -> syn::Result<Self> {
let struct_item = syn::parse(input)?;
Self::with_struct(args, struct_item)
Self::with_struct(args, struct_item, mode)
}
pub(crate) fn with_struct(
args: proc_macro::TokenStream,
struct_item: syn::ItemStruct,
mode: Mode<M>,
) -> syn::Result<Self> {
let args = syn::parse(args)?;
let args: Options<A> = syn::parse(args)?;
let fields = Self::extract_options(&struct_item)?;
check_singleton(&mode, args.singleton.as_ref(), struct_item.span())?;
Ok(Self {
args,
_mode: mode,
struct_item,
fields,
})
@ -123,6 +128,20 @@ impl<A: AllowedOptions> SalsaStruct<A> {
self.args.jar_ty()
}
/// checks if the "singleton" flag was set
pub(crate) fn is_isingleton(&self) -> bool {
self.args.singleton.is_some()
}
pub(crate) fn num_fields(&self) -> usize {
self.fields.len()
}
pub(crate) fn struct_span(&self) -> Span {
self.struct_item.span()
}
pub(crate) fn db_dyn_ty(&self) -> syn::Type {
let jar_ty = self.jar_ty();
parse_quote! {
@ -431,3 +450,15 @@ impl SalsaField {
!self.has_no_eq_attr
}
}
pub(crate) fn check_singleton<M: AllowedModes>(mode: &Mode<M>, sing: Option<&syn::Ident>, s_span: Span) -> syn::Result<()> {
if !mode.singleton_allowed() && sing.is_some() {
Err(syn::Error::new(
s_span,
format!("`Singleton` not allowed for this Salsa struct type"),
))
} else {
Ok(())
}
}

View file

@ -72,6 +72,8 @@ impl crate::options::AllowedOptions for TrackedFn {
const NO_EQ: bool = true;
const SINGLETON: bool = false;
const JAR: bool = true;
const DATA: bool = false;

View file

@ -1,6 +1,9 @@
use proc_macro2::{Literal, TokenStream};
use crate::salsa_struct::{SalsaField, SalsaStruct};
use crate::{
modes::Mode,
salsa_struct::{SalsaField, SalsaStruct},
};
/// For an tracked struct `Foo` with fields `f1: T1, ..., fN: TN`, we generate...
///
@ -11,7 +14,10 @@ pub(crate) fn tracked(
args: proc_macro::TokenStream,
struct_item: syn::ItemStruct,
) -> proc_macro::TokenStream {
match SalsaStruct::with_struct(args, struct_item)
let mode = Mode {
..Default::default()
};
match SalsaStruct::with_struct(args, struct_item, mode)
.and_then(|el| TrackedStruct(el).generate_tracked())
{
Ok(s) => s.into(),
@ -19,10 +25,10 @@ pub(crate) fn tracked(
}
}
struct TrackedStruct(SalsaStruct<Self>);
struct TrackedStruct(SalsaStruct<Self, Self>);
impl std::ops::Deref for TrackedStruct {
type Target = SalsaStruct<Self>;
type Target = SalsaStruct<Self, Self>;
fn deref(&self) -> &Self::Target {
&self.0
@ -36,6 +42,8 @@ impl crate::options::AllowedOptions for TrackedStruct {
const NO_EQ: bool = false;
const SINGLETON: bool = false;
const JAR: bool = true;
const DATA: bool = true;
@ -49,6 +57,16 @@ impl crate::options::AllowedOptions for TrackedStruct {
const CONSTRUCTOR_NAME: bool = true;
}
impl crate::modes::AllowedModes for TrackedStruct {
const TRACKED: bool = true;
const INPUT: bool = false;
const INTERNED: bool = false;
const ACCUMULATOR: bool = false;
}
impl TrackedStruct {
fn generate_tracked(&self) -> syn::Result<TokenStream> {
self.validate_tracked()?;

View file

@ -46,6 +46,20 @@ where
self.counter += 1;
Id::from_id(crate::Id::from_u32(next_id))
}
pub fn new_singleton_input(&mut self, _runtime: &mut Runtime) -> Id {
if self.counter >= 1 { // already exists
Id::from_id(crate::Id::from_u32(self.counter - 1))
} else {
self.new_input(_runtime)
}
}
pub fn get_singleton_input(&self, _runtime: &Runtime) -> Option<Id> {
(self.counter > 0).then(|| Id::from_id(crate::Id::from_id(crate::Id::from_u32(self.counter - 1))))
}
}
impl<DB: ?Sized, Id> Ingredient<DB> for InputIngredient<Id>

View file

@ -20,7 +20,7 @@ struct Jar(
trait Db: salsa::DbWithJar<Jar> + HasLogger {}
#[salsa::input]
#[salsa::input(singleton)]
struct MyInput {
field: u32,
}

View file

@ -0,0 +1,53 @@
//! Basic deletion test:
//!
//! * entities not created in a revision are deleted, as is any memoized data keyed on them.
use salsa_2022_tests::{HasLogger, Logger};
use test_log::test;
#[salsa::jar(db = Db)]
struct Jar(
MyInput,
);
trait Db: salsa::DbWithJar<Jar> + HasLogger {}
#[salsa::input(singleton)]
struct MyInput {
field: u32,
}
#[salsa::db(Jar)]
#[derive(Default)]
struct Database {
storage: salsa::Storage<Self>,
logger: Logger,
}
impl salsa::Database for Database {}
impl Db for Database {}
impl HasLogger for Database {
fn logger(&self) -> &Logger {
&self.logger
}
}
#[test]
fn basic() {
let mut db = Database::default();
let input1 = MyInput::new(&mut db, 3);
let input2 = MyInput::get(&db);
assert_eq!(input1, input2);
let input3 = MyInput::try_get(&db);
assert_eq!(Some(input1), input3);
let input4 = MyInput::new(&mut db, 3);
assert_eq!(input2, input4)
}