Skip to content

Commit 9840abf

Browse files
committed
add try_ok method to MemoRef
1 parent 74bb1a2 commit 9840abf

File tree

8 files changed

+201
-47
lines changed

8 files changed

+201
-47
lines changed

crates/graphql_network_protocol/src/read_schema.rs

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -35,7 +35,7 @@ pub fn parse_graphql_schema<TNetworkProtocol: NetworkProtocol + 'static>(
3535
.iter()
3636
{
3737
let extensions_document =
38-
parse_schema_extensions_file(db, *schema_extension_source_id).to_owned()?;
38+
parse_schema_extensions_file(db, *schema_extension_source_id).try_ok()?;
3939
schema_extensions.insert(*relative_path, extensions_document);
4040
}
4141

@@ -46,7 +46,7 @@ pub fn parse_graphql_schema<TNetworkProtocol: NetworkProtocol + 'static>(
4646
pub fn parse_schema_extensions_file<TNetworkProtocol: NetworkProtocol + 'static>(
4747
db: &IsographDatabase<TNetworkProtocol>,
4848
schema_extension_source_id: SourceId<SchemaSource>,
49-
) -> Result<MemoRef<GraphQLTypeSystemExtensionDocument>, WithLocation<SchemaParseError>> {
49+
) -> Result<GraphQLTypeSystemExtensionDocument, WithLocation<SchemaParseError>> {
5050
let SchemaSource {
5151
content,
5252
text_source,
@@ -55,5 +55,5 @@ pub fn parse_schema_extensions_file<TNetworkProtocol: NetworkProtocol + 'static>
5555
let schema_extensions = parse_schema_extensions(content, *text_source)
5656
.map_err(|with_span| with_span.to_with_location(*text_source))?;
5757

58-
Ok(db.intern(schema_extensions))
58+
Ok(schema_extensions)
5959
}

crates/pico/src/view.rs renamed to crates/pico/src/field_view.rs

Lines changed: 10 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -6,16 +6,16 @@ pub trait Counter: Singleton + Default + Copy + Eq + 'static {
66
fn increment(self) -> Self;
77
}
88

9-
pub type Projector<Db, T> = for<'a> fn(&'a Db) -> &'a T;
9+
pub type FieldProjector<Db, T> = for<'a> fn(&'a Db) -> &'a T;
1010

11-
pub struct View<'a, Db: Database, T, C: Counter> {
11+
pub struct FieldView<'a, Db: Database, T, C: Counter> {
1212
db: &'a Db,
13-
projector: Projector<Db, T>,
13+
projector: FieldProjector<Db, T>,
1414
phantom: PhantomData<C>,
1515
}
1616

17-
impl<'a, Db: Database, T, C: Counter> View<'a, Db, T, C> {
18-
pub fn new(db: &'a Db, projector: Projector<Db, T>) -> Self {
17+
impl<'a, Db: Database, T, C: Counter> FieldView<'a, Db, T, C> {
18+
pub fn new(db: &'a Db, projector: FieldProjector<Db, T>) -> Self {
1919
Self {
2020
db,
2121
projector,
@@ -44,16 +44,16 @@ impl<'a, Db: Database, T, C: Counter> View<'a, Db, T, C> {
4444
}
4545
}
4646

47-
pub type ProjectorMut<Db, T> = for<'a> fn(&'a mut Db) -> &'a mut T;
47+
pub type FieldProjectorMut<Db, T> = for<'a> fn(&'a mut Db) -> &'a mut T;
4848

49-
pub struct MutView<'a, Db: Database, T, C: Counter> {
49+
pub struct FieldViewMut<'a, Db: Database, T, C: Counter> {
5050
db: &'a mut Db,
51-
projector: ProjectorMut<Db, T>,
51+
projector: FieldProjectorMut<Db, T>,
5252
phantom: PhantomData<C>,
5353
}
5454

55-
impl<'a, Db: Database, T, C: Counter> MutView<'a, Db, T, C> {
56-
pub fn new(db: &'a mut Db, projector: ProjectorMut<Db, T>) -> Self {
55+
impl<'a, Db: Database, T, C: Counter> FieldViewMut<'a, Db, T, C> {
56+
pub fn new(db: &'a mut Db, projector: FieldProjectorMut<Db, T>) -> Self {
5757
Self {
5858
db,
5959
projector,

crates/pico/src/lib.rs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,21 +4,21 @@ mod derived_node;
44
mod dyn_eq;
55
mod epoch;
66
mod execute_memoized_function;
7+
mod field_view;
78
mod garbage_collection;
89
mod index;
910
mod intern;
1011
pub mod macro_fns;
1112
mod memo_ref;
1213
mod retained_query;
1314
mod source;
14-
mod view;
1515

1616
pub use database::*;
1717
pub use derived_node::*;
1818
pub use dyn_eq::*;
1919
pub use execute_memoized_function::*;
20+
pub use field_view::*;
2021
pub use intern::*;
2122
pub use memo_ref::*;
2223
pub use retained_query::*;
2324
pub use source::*;
24-
pub use view::*;

crates/pico/src/memo_ref.rs

Lines changed: 52 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,38 @@
1-
use std::{marker::PhantomData, ops::Deref};
1+
use std::{
2+
any::Any,
3+
hash::{Hash, Hasher},
4+
marker::PhantomData,
5+
ops::Deref,
6+
};
27

8+
use crate::{DatabaseDyn, DerivedNodeId, ParamId, dependency::NodeKind};
39
use intern::InternId;
410

5-
use crate::{DatabaseDyn, DerivedNodeId, ParamId, dependency::NodeKind};
11+
type MemoRefProjector<T> = for<'a> fn(&'a dyn Any) -> &'a T;
12+
13+
#[inline(always)]
14+
fn project_identity<T: 'static>(value: &dyn Any) -> &T {
15+
value
16+
.downcast_ref::<T>()
17+
.expect("MemoRef: underlying value has unexpected type")
18+
}
19+
20+
#[inline(always)]
21+
fn project_result_ok<T: 'static, E: 'static>(value: &dyn Any) -> &T {
22+
match value
23+
.downcast_ref::<Result<T, E>>()
24+
.expect("MemoRef<Result<..>>: underlying value has unexpected type")
25+
{
26+
Ok(t) => t,
27+
Err(_) => unreachable!("Ok projection used only after Ok check"),
28+
}
29+
}
630

731
#[derive(Debug)]
832
pub struct MemoRef<T> {
933
pub(crate) db: *const dyn DatabaseDyn,
1034
pub(crate) derived_node_id: DerivedNodeId,
35+
projector: MemoRefProjector<T>,
1136
phantom: PhantomData<T>,
1237
}
1338

@@ -27,12 +52,22 @@ impl<T> PartialEq for MemoRef<T> {
2752

2853
impl<T> Eq for MemoRef<T> {}
2954

55+
#[allow(clippy::unnecessary_cast)]
56+
impl<T> Hash for MemoRef<T> {
57+
fn hash<H: Hasher>(&self, state: &mut H) {
58+
let data_ptr = self.db as *const dyn DatabaseDyn as *const ();
59+
data_ptr.hash(state);
60+
self.derived_node_id.hash(state);
61+
}
62+
}
63+
3064
#[allow(clippy::unnecessary_cast)]
3165
impl<T: 'static + Clone> MemoRef<T> {
3266
pub fn new(db: &dyn DatabaseDyn, derived_node_id: DerivedNodeId) -> Self {
3367
Self {
3468
db: db as *const _ as *const dyn DatabaseDyn,
3569
derived_node_id,
70+
projector: project_identity::<T>,
3671
phantom: PhantomData,
3772
}
3873
}
@@ -63,6 +98,20 @@ impl<T: 'static> Deref for MemoRef<T> {
6398
NodeKind::Derived(self.derived_node_id),
6499
revision.time_updated,
65100
);
66-
value.downcast_ref::<T>().unwrap()
101+
(self.projector)(value)
102+
}
103+
}
104+
105+
impl<T: 'static, E: 'static + Clone> MemoRef<Result<T, E>> {
106+
pub fn try_ok(self) -> Result<MemoRef<T>, E> {
107+
match self.deref() {
108+
Ok(_) => Ok(MemoRef {
109+
db: self.db,
110+
derived_node_id: self.derived_node_id,
111+
projector: project_result_ok::<T, E>,
112+
phantom: PhantomData,
113+
}),
114+
Err(err) => Err(err.clone()),
115+
}
67116
}
68117
}

crates/pico/tests/intern.rs

Lines changed: 2 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -39,23 +39,19 @@ enum ProcessInputError {
3939
}
4040

4141
#[memo]
42-
fn first_letter(
43-
db: &TestDatabase,
44-
input_id: SourceId<Input>,
45-
) -> Result<MemoRef<char>, FirstLetterError> {
42+
fn first_letter(db: &TestDatabase, input_id: SourceId<Input>) -> Result<char, FirstLetterError> {
4643
db.get(input_id)
4744
.value
4845
.chars()
4946
.next()
5047
.ok_or(FirstLetterError::EmptyString)
51-
.map(|v| db.intern(v))
5248
}
5349

5450
#[memo]
5551
fn process_input(
5652
db: &TestDatabase,
5753
input_id: SourceId<Input>,
5854
) -> Result<MemoRef<char>, ProcessInputError> {
59-
let result = first_letter(db, input_id).to_owned()?;
55+
let result = first_letter(db, input_id).try_ok()?;
6056
Ok(result)
6157
}

crates/pico/tests/try_ok.rs

Lines changed: 125 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,125 @@
1+
use pico::{Database, MemoRef, SourceId, Storage};
2+
use pico_macros::{Db, Source, memo};
3+
use std::sync::atomic::{AtomicUsize, Ordering};
4+
use thiserror::Error;
5+
6+
static TRACK_CLONE_COUNT: AtomicUsize = AtomicUsize::new(0);
7+
8+
#[derive(Db, Default)]
9+
struct TestDatabase {
10+
storage: Storage<Self>,
11+
}
12+
13+
#[test]
14+
fn try_ok_projects_ok_value() {
15+
let mut db = TestDatabase::default();
16+
17+
let id = db.set(Input {
18+
key: "key",
19+
value: "asdf".to_string(),
20+
});
21+
22+
assert_eq!(*process_input(&db, id).unwrap(), 'a');
23+
}
24+
25+
#[test]
26+
fn projected_memo_correctly_restored() {
27+
let mut db = TestDatabase::default();
28+
29+
let id = db.set(Input {
30+
key: "key",
31+
value: "asdf".to_string(),
32+
});
33+
34+
let result = process_input(&db, id).unwrap();
35+
assert_eq!(*consume_result(&db, result), 'a');
36+
}
37+
38+
#[derive(Debug, Clone, PartialEq, Eq, Source)]
39+
struct Input {
40+
#[key]
41+
pub key: &'static str,
42+
pub value: String,
43+
}
44+
45+
#[derive(Debug, Clone, Error, PartialEq, Eq)]
46+
enum FirstLetterError {
47+
#[error("empty string")]
48+
EmptyString,
49+
}
50+
51+
#[derive(Debug, Clone, Error, PartialEq, Eq)]
52+
enum ProcessInputError {
53+
#[error("cannot process input")]
54+
ReadError(#[from] FirstLetterError),
55+
}
56+
57+
#[memo]
58+
fn first_letter(db: &TestDatabase, input_id: SourceId<Input>) -> Result<char, FirstLetterError> {
59+
db.get(input_id)
60+
.value
61+
.chars()
62+
.next()
63+
.ok_or(FirstLetterError::EmptyString)
64+
}
65+
66+
fn process_input(
67+
db: &TestDatabase,
68+
input_id: SourceId<Input>,
69+
) -> Result<MemoRef<char>, ProcessInputError> {
70+
let result = first_letter(db, input_id).try_ok()?;
71+
Ok(result)
72+
}
73+
74+
#[memo]
75+
fn consume_result(db: &TestDatabase, first_letter: MemoRef<char>) -> char {
76+
let result = *first_letter;
77+
result
78+
}
79+
80+
#[test]
81+
fn try_ok_never_clones_ok_value() {
82+
let db = TestDatabase::default();
83+
84+
let result = ok_value(&db).try_ok().expect("expected ok result");
85+
let _ = &*result;
86+
87+
match err_value(&db).try_ok() {
88+
Ok(_) => panic!("expected error"),
89+
Err(_err) => {}
90+
}
91+
92+
assert_eq!(TRACK_CLONE_COUNT.load(Ordering::SeqCst), 1);
93+
}
94+
95+
#[memo]
96+
fn ok_value(_db: &TestDatabase) -> Result<PanicOnClone, TrackCloneError> {
97+
Ok(PanicOnClone)
98+
}
99+
100+
#[memo]
101+
fn err_value(_db: &TestDatabase) -> Result<PanicOnClone, TrackCloneError> {
102+
Err(TrackCloneError::Count)
103+
}
104+
105+
#[derive(Debug, PartialEq, Eq)]
106+
struct PanicOnClone;
107+
108+
impl Clone for PanicOnClone {
109+
fn clone(&self) -> Self {
110+
panic!("PanicOnClone should not be cloned");
111+
}
112+
}
113+
114+
#[derive(Debug, Error, PartialEq, Eq)]
115+
enum TrackCloneError {
116+
#[error("error cloned")]
117+
Count,
118+
}
119+
120+
impl Clone for TrackCloneError {
121+
fn clone(&self) -> Self {
122+
TRACK_CLONE_COUNT.fetch_add(1, Ordering::SeqCst);
123+
TrackCloneError::Count
124+
}
125+
}

crates/pico_macros/src/db_macro.rs

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -80,17 +80,17 @@ pub(crate) fn db_macro(item: TokenStream) -> TokenStream {
8080
});
8181

8282
struct_impl.push(quote! {
83-
const #proj_ident: ::pico::Projector<#struct_ident #type_generics, #field_ty> = |db: &#struct_ident #type_generics| &db.#field_ident;
84-
const #proj_ident_mut: ::pico::ProjectorMut<#struct_ident #type_generics, #field_ty> = |db: &mut #struct_ident #type_generics| &mut db.#field_ident;
83+
const #proj_ident: ::pico::FieldProjector<#struct_ident #type_generics, #field_ty> = |db: &#struct_ident #type_generics| &db.#field_ident;
84+
const #proj_ident_mut: ::pico::FieldProjectorMut<#struct_ident #type_generics, #field_ty> = |db: &mut #struct_ident #type_generics| &mut db.#field_ident;
8585

8686
#[inline]
87-
pub fn #get_ident(&self) -> ::pico::View<'_, #struct_ident #type_generics, #field_ty, #counter_ident> {
88-
::pico::View::new(self, Self::#proj_ident)
87+
pub fn #get_ident(&self) -> ::pico::FieldView<'_, #struct_ident #type_generics, #field_ty, #counter_ident> {
88+
::pico::FieldView::new(self, Self::#proj_ident)
8989
}
9090

9191
#[inline]
92-
pub fn #get_ident_mut(&mut self) -> ::pico::MutView<'_, #struct_ident #type_generics, #field_ty, #counter_ident> {
93-
::pico::MutView::new(self, Self::#proj_ident_mut)
92+
pub fn #get_ident_mut(&mut self) -> ::pico::FieldViewMut<'_, #struct_ident #type_generics, #field_ty, #counter_ident> {
93+
::pico::FieldViewMut::new(self, Self::#proj_ident_mut)
9494
}
9595
});
9696
}

0 commit comments

Comments
 (0)