Skip to content

Commit 406d763

Browse files
authored
linera-views: make the context an associated type (#4031)
## Motivation Since I'm working with `linera-views` pretty heavily, I'd like to make it easier to modify. One source of pain is that we parameterize the `View` trait by its context type. This is semantically incorrect, as each view can have only a single context type that is fully determined by its fully-instantiated type (specifically, by the type of the context contained within the view), and pragmatically speaking means we can't write down the requirements on that context in the `View` trait itself, and have to duplicate them at use sites, which makes modifying those bounds painful. ## Proposal Make the `Context` an associated type on the `View` trait, with all necessary bounds (except some `Sync`s: see below). ## Test Plan CI. ## Release Plan - Nothing to do / These changes follow the usual release cycle. ## Links - [reviewer checklist](https://github.com/linera-io/linera-protocol/blob/main/CONTRIBUTING.md#reviewer-checklist) ## Future work There remains a `V: View + Sync` bound on some of the implementers (e.g. `MapView`) that forbids using those implementers on the Web, where views are not `Sync` as the `IndexedDbStore` is not `Sync`. Removing this bound is non-trivial because there is a `ReadKeyValueStore` implementation for `storage-service` that is decidedly not `Sync`. I will need to make that implementation `Sync`, remove the need for the `Sync` bound, or do some complex conditional compilation, but this is at least a step in the right direction.
1 parent e2e7782 commit 406d763

File tree

72 files changed

+1740
-760
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

72 files changed

+1740
-760
lines changed

linera-indexer/lib/src/plugin.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -63,7 +63,7 @@ pub fn route<Q: ObjectType + 'static>(name: &str, query: Q, app: axum::Router) -
6363
.layer(tower_http::cors::CorsLayer::permissive())
6464
}
6565

66-
pub async fn load<S, V: View<ViewContext<(), S>>>(
66+
pub async fn load<S, V: View<Context = ViewContext<(), S>>>(
6767
store: S,
6868
name: &str,
6969
) -> Result<Arc<Mutex<V>>, IndexerError>

linera-views-derive/src/lib.rs

Lines changed: 46 additions & 94 deletions
Original file line numberDiff line numberDiff line change
@@ -6,57 +6,30 @@
66
use proc_macro::TokenStream;
77
use proc_macro2::{Span, TokenStream as TokenStream2};
88
use quote::{format_ident, quote};
9-
use syn::{parse_macro_input, parse_quote, ItemStruct, Type, TypePath};
9+
use syn::{parse_macro_input, parse_quote, ItemStruct, Type};
1010

1111
#[derive(Debug, deluxe::ParseAttributes)]
1212
#[deluxe(attributes(view))]
1313
struct StructAttrs {
1414
context: Option<syn::Type>,
1515
}
1616

17-
struct ContextAndConstraints<'a> {
18-
context: syn::Type,
19-
context_constraints: Vec<syn::WherePredicate>,
17+
struct Constraints<'a> {
2018
input_constraints: Vec<&'a syn::WherePredicate>,
2119
impl_generics: syn::ImplGenerics<'a>,
2220
type_generics: syn::TypeGenerics<'a>,
2321
}
2422

25-
impl<'a> ContextAndConstraints<'a> {
23+
impl<'a> Constraints<'a> {
2624
fn get(item: &'a syn::ItemStruct) -> Self {
27-
let attrs: StructAttrs = deluxe::parse_attributes(item).unwrap();
28-
2925
let (impl_generics, type_generics, maybe_where_clause) = item.generics.split_for_impl();
3026
let input_constraints = maybe_where_clause
3127
.map(|w| w.predicates.iter())
3228
.into_iter()
3329
.flatten()
3430
.collect();
3531

36-
let (context, context_constraints) = if let Some(context) = attrs.context {
37-
(context, vec![])
38-
} else {
39-
let first_type_param = item
40-
.generics
41-
.type_params()
42-
.map(|param| &param.ident)
43-
.next()
44-
.expect("no context provided and no type parameters");
45-
let context = Type::Path(TypePath {
46-
qself: None,
47-
path: first_type_param.clone().into(),
48-
});
49-
50-
let constraints = vec![parse_quote! {
51-
#context: linera_views::context::Context + Send + Sync + Clone + 'static
52-
}];
53-
54-
(context, constraints)
55-
};
56-
5732
Self {
58-
context,
59-
context_constraints,
6033
input_constraints,
6134
impl_generics,
6235
type_generics,
@@ -75,14 +48,25 @@ fn get_extended_entry(e: Type) -> TokenStream2 {
7548
}
7649

7750
fn generate_view_code(input: ItemStruct, root: bool) -> TokenStream2 {
78-
let ContextAndConstraints {
79-
context,
80-
context_constraints,
51+
let Constraints {
8152
input_constraints,
8253
impl_generics,
8354
type_generics,
84-
} = ContextAndConstraints::get(&input);
55+
} = Constraints::get(&input);
56+
57+
let attrs: StructAttrs = deluxe::parse_attributes(&input).unwrap();
58+
let context = attrs.context.unwrap_or_else(|| {
59+
let ident = &input
60+
.generics
61+
.type_params()
62+
.next()
63+
.expect("no `context` given and no type parameters")
64+
.ident;
65+
parse_quote! { #ident }
66+
});
67+
8568
let struct_name = &input.ident;
69+
let field_types: Vec<_> = input.fields.iter().map(|field| &field.ty).collect();
8670

8771
let mut name_quotes = Vec::new();
8872
let mut rollback_quotes = Vec::new();
@@ -93,15 +77,12 @@ fn generate_view_code(input: ItemStruct, root: bool) -> TokenStream2 {
9377
let mut num_init_keys_quotes = Vec::new();
9478
let mut pre_load_keys_quotes = Vec::new();
9579
let mut post_load_keys_quotes = Vec::new();
96-
let mut field_constraints = vec![];
9780
for (idx, e) in input.fields.iter().enumerate() {
9881
let name = e.ident.clone().unwrap();
99-
let ty = &e.ty;
10082
let test_flush_ident = format_ident!("deleted{}", idx);
10183
let idx_lit = syn::LitInt::new(&idx.to_string(), Span::call_site());
10284
let g = get_extended_entry(e.ty.clone());
10385
name_quotes.push(quote! { #name });
104-
field_constraints.push(quote! { #ty: linera_views::views::View<#context> });
10586
rollback_quotes.push(quote! { self.#name.rollback(); });
10687
flush_quotes.push(quote! { let #test_flush_ident = self.#name.flush(batch)?; });
10788
test_flush_quotes.push(quote! { #test_flush_ident });
@@ -147,14 +128,15 @@ fn generate_view_code(input: ItemStruct, root: bool) -> TokenStream2 {
147128
};
148129

149130
quote! {
150-
impl #impl_generics linera_views::views::View<#context> for #struct_name #type_generics
131+
impl #impl_generics linera_views::views::View for #struct_name #type_generics
151132
where
133+
#context: linera_views::context::Context,
152134
#(#input_constraints,)*
153-
#(#context_constraints,)*
154-
#(#field_constraints,)*
155-
Self: Send + Sync,
135+
#(#field_types: linera_views::views::View<Context = #context>,)*
156136
{
157-
const NUM_INIT_KEYS: usize = #(#num_init_keys_quotes)+*;
137+
const NUM_INIT_KEYS: usize = #(<#field_types as linera_views::views::View>::NUM_INIT_KEYS)+*;
138+
139+
type Context = #context;
158140

159141
fn context(&self) -> &#context {
160142
use linera_views::views::View;
@@ -211,21 +193,12 @@ fn generate_view_code(input: ItemStruct, root: bool) -> TokenStream2 {
211193
}
212194

213195
fn generate_root_view_code(input: ItemStruct) -> TokenStream2 {
214-
let ContextAndConstraints {
215-
context,
216-
context_constraints,
196+
let Constraints {
217197
input_constraints,
218198
impl_generics,
219199
type_generics,
220-
} = ContextAndConstraints::get(&input);
200+
} = Constraints::get(&input);
221201
let struct_name = &input.ident;
222-
let mut flushes = Vec::new();
223-
let mut deletes = Vec::new();
224-
for e in &input.fields {
225-
let name = e.ident.clone().unwrap();
226-
flushes.push(quote! { self.#name.flush(&mut batch)?; });
227-
deletes.push(quote! { self.#name.delete(batch); });
228-
}
229202

230203
let increment_counter = if cfg!(feature = "metrics") {
231204
quote! {
@@ -241,17 +214,16 @@ fn generate_root_view_code(input: ItemStruct) -> TokenStream2 {
241214
};
242215

243216
quote! {
244-
impl #impl_generics linera_views::views::RootView<#context> for #struct_name #type_generics
217+
impl #impl_generics linera_views::views::RootView for #struct_name #type_generics
245218
where
246219
#(#input_constraints,)*
247-
#(#context_constraints,)*
248-
Self: Send + Sync,
220+
Self: linera_views::views::View + Sync,
249221
{
250222
async fn save(&mut self) -> Result<(), linera_views::ViewError> {
251223
use linera_views::{context::Context, batch::Batch, store::WritableKeyValueStore as _, views::View};
252224
#increment_counter
253225
let mut batch = Batch::new();
254-
#(#flushes)*
226+
self.flush(&mut batch)?;
255227
if !batch.is_empty() {
256228
self.context().store().write_batch(batch).await?;
257229
}
@@ -261,28 +233,15 @@ fn generate_root_view_code(input: ItemStruct) -> TokenStream2 {
261233
}
262234
}
263235

264-
fn hash_view_constraints(input: &ItemStruct, context: &syn::Type) -> Vec<syn::WherePredicate> {
265-
input
266-
.fields
267-
.iter()
268-
.map(|field| {
269-
let ty = &field.ty;
270-
parse_quote! { #ty: linera_views::views::HashableView<#context> }
271-
})
272-
.collect()
273-
}
274-
275236
fn generate_hash_view_code(input: ItemStruct) -> TokenStream2 {
276-
let ContextAndConstraints {
277-
context,
278-
context_constraints,
237+
let Constraints {
279238
input_constraints,
280239
impl_generics,
281240
type_generics,
282-
} = ContextAndConstraints::get(&input);
241+
} = Constraints::get(&input);
283242
let struct_name = &input.ident;
284-
let hash_constraints = hash_view_constraints(&input, &context);
285243

244+
let field_types = input.fields.iter().map(|field| &field.ty);
286245
let mut field_hashes_mut = Vec::new();
287246
let mut field_hashes = Vec::new();
288247
for e in &input.fields {
@@ -292,12 +251,11 @@ fn generate_hash_view_code(input: ItemStruct) -> TokenStream2 {
292251
}
293252

294253
quote! {
295-
impl #impl_generics linera_views::views::HashableView<#context> for #struct_name #type_generics
254+
impl #impl_generics linera_views::views::HashableView for #struct_name #type_generics
296255
where
256+
#(#field_types: linera_views::views::HashableView,)*
297257
#(#input_constraints,)*
298-
#(#context_constraints,)*
299-
#(#hash_constraints,)*
300-
Self: Send + Sync,
258+
Self: linera_views::views::View + Sync,
301259
{
302260
type Hasher = linera_views::sha3::Sha3_256;
303261

@@ -321,24 +279,21 @@ fn generate_hash_view_code(input: ItemStruct) -> TokenStream2 {
321279
}
322280

323281
fn generate_crypto_hash_code(input: ItemStruct) -> TokenStream2 {
324-
let ContextAndConstraints {
325-
context,
326-
context_constraints,
282+
let Constraints {
327283
input_constraints,
328284
impl_generics,
329285
type_generics,
330-
} = ContextAndConstraints::get(&input);
331-
let hash_constraints = hash_view_constraints(&input, &context);
286+
} = Constraints::get(&input);
287+
let field_types = input.fields.iter().map(|field| &field.ty);
332288
let struct_name = &input.ident;
333289
let hash_type = syn::Ident::new(&format!("{struct_name}Hash"), Span::call_site());
334290
quote! {
335-
impl #impl_generics linera_views::views::CryptoHashView<#context>
291+
impl #impl_generics linera_views::views::CryptoHashView
336292
for #struct_name #type_generics
337293
where
294+
#(#field_types: linera_views::views::HashableView,)*
338295
#(#input_constraints,)*
339-
#(#context_constraints,)*
340-
#(#hash_constraints,)*
341-
Self: Send + Sync,
296+
Self: linera_views::views::View + Sync,
342297
{
343298
async fn crypto_hash(&self) -> Result<linera_base::crypto::CryptoHash, linera_views::ViewError> {
344299
use linera_base::crypto::{BcsHashable, CryptoHash};
@@ -376,13 +331,11 @@ fn generate_crypto_hash_code(input: ItemStruct) -> TokenStream2 {
376331
}
377332

378333
fn generate_clonable_view_code(input: ItemStruct) -> TokenStream2 {
379-
let ContextAndConstraints {
380-
context,
381-
context_constraints,
334+
let Constraints {
382335
input_constraints,
383336
impl_generics,
384337
type_generics,
385-
} = ContextAndConstraints::get(&input);
338+
} = Constraints::get(&input);
386339
let struct_name = &input.ident;
387340

388341
let mut clone_constraints = vec![];
@@ -391,17 +344,16 @@ fn generate_clonable_view_code(input: ItemStruct) -> TokenStream2 {
391344
for field in &input.fields {
392345
let name = &field.ident;
393346
let ty = &field.ty;
394-
clone_constraints.push(quote! { #ty: ClonableView<#context> });
347+
clone_constraints.push(quote! { #ty: ClonableView });
395348
clone_fields.push(quote! { #name: self.#name.clone_unchecked()? });
396349
}
397350

398351
quote! {
399-
impl #impl_generics linera_views::views::ClonableView<#context> for #struct_name #type_generics
352+
impl #impl_generics linera_views::views::ClonableView for #struct_name #type_generics
400353
where
401-
#(#context_constraints,)*
402354
#(#input_constraints,)*
403355
#(#clone_constraints,)*
404-
Self: Send + Sync,
356+
Self: linera_views::views::View + Sync,
405357
{
406358
fn clone_unchecked(&mut self) -> Result<Self, linera_views::ViewError> {
407359
Ok(Self {

linera-views-derive/src/snapshots/linera_views_derive__tests__generate_clonable_view_code-2.snap

Lines changed: 4 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -2,13 +2,12 @@
22
source: linera-views-derive/src/lib.rs
33
expression: pretty(generate_clonable_view_code(input))
44
---
5-
impl<C, MyParam> linera_views::views::ClonableView<C> for TestView<C, MyParam>
5+
impl<C, MyParam> linera_views::views::ClonableView for TestView<C, MyParam>
66
where
7-
C: linera_views::context::Context + Send + Sync + Clone + 'static,
87
MyParam: Send + Sync + 'static,
9-
RegisterView<C, usize>: ClonableView<C>,
10-
CollectionView<C, usize, RegisterView<C, usize>>: ClonableView<C>,
11-
Self: Send + Sync,
8+
RegisterView<C, usize>: ClonableView,
9+
CollectionView<C, usize, RegisterView<C, usize>>: ClonableView,
10+
Self: linera_views::views::View + Sync,
1211
{
1312
fn clone_unchecked(&mut self) -> Result<Self, linera_views::ViewError> {
1413
Ok(Self {

linera-views-derive/src/snapshots/linera_views_derive__tests__generate_clonable_view_code-3.snap

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -2,15 +2,15 @@
22
source: linera-views-derive/src/lib.rs
33
expression: pretty(generate_clonable_view_code(input))
44
---
5-
impl linera_views::views::ClonableView<CustomContext> for TestView
5+
impl linera_views::views::ClonableView for TestView
66
where
7-
RegisterView<CustomContext, usize>: ClonableView<CustomContext>,
7+
RegisterView<CustomContext, usize>: ClonableView,
88
CollectionView<
99
CustomContext,
1010
usize,
1111
RegisterView<CustomContext, usize>,
12-
>: ClonableView<CustomContext>,
13-
Self: Send + Sync,
12+
>: ClonableView,
13+
Self: linera_views::views::View + Sync,
1414
{
1515
fn clone_unchecked(&mut self) -> Result<Self, linera_views::ViewError> {
1616
Ok(Self {

linera-views-derive/src/snapshots/linera_views_derive__tests__generate_clonable_view_code-4.snap

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -2,16 +2,16 @@
22
source: linera-views-derive/src/lib.rs
33
expression: pretty(generate_clonable_view_code(input))
44
---
5-
impl<MyParam> linera_views::views::ClonableView<CustomContext> for TestView<MyParam>
5+
impl<MyParam> linera_views::views::ClonableView for TestView<MyParam>
66
where
77
MyParam: Send + Sync + 'static,
8-
RegisterView<CustomContext, usize>: ClonableView<CustomContext>,
8+
RegisterView<CustomContext, usize>: ClonableView,
99
CollectionView<
1010
CustomContext,
1111
usize,
1212
RegisterView<CustomContext, usize>,
13-
>: ClonableView<CustomContext>,
14-
Self: Send + Sync,
13+
>: ClonableView,
14+
Self: linera_views::views::View + Sync,
1515
{
1616
fn clone_unchecked(&mut self) -> Result<Self, linera_views::ViewError> {
1717
Ok(Self {

linera-views-derive/src/snapshots/linera_views_derive__tests__generate_clonable_view_code-5.snap

Lines changed: 4 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -2,18 +2,15 @@
22
source: linera-views-derive/src/lib.rs
33
expression: pretty(generate_clonable_view_code(input))
44
---
5-
impl linera_views::views::ClonableView<custom::path::to::ContextType> for TestView
5+
impl linera_views::views::ClonableView for TestView
66
where
7-
RegisterView<
8-
custom::path::to::ContextType,
9-
usize,
10-
>: ClonableView<custom::path::to::ContextType>,
7+
RegisterView<custom::path::to::ContextType, usize>: ClonableView,
118
CollectionView<
129
custom::path::to::ContextType,
1310
usize,
1411
RegisterView<custom::path::to::ContextType, usize>,
15-
>: ClonableView<custom::path::to::ContextType>,
16-
Self: Send + Sync,
12+
>: ClonableView,
13+
Self: linera_views::views::View + Sync,
1714
{
1815
fn clone_unchecked(&mut self) -> Result<Self, linera_views::ViewError> {
1916
Ok(Self {

0 commit comments

Comments
 (0)