1use proc_macro::TokenStream;
7use proc_macro2::{Span, TokenStream as TokenStream2};
8use quote::{format_ident, quote};
9use syn::{parse_macro_input, parse_quote, Error, ItemStruct, Type};
10
11#[derive(Debug, deluxe::ParseAttributes)]
12#[deluxe(attributes(view))]
13struct StructAttrs {
14 context: Option<syn::Type>,
15}
16
17struct Constraints<'a> {
18 input_constraints: Vec<&'a syn::WherePredicate>,
19 impl_generics: syn::ImplGenerics<'a>,
20 type_generics: syn::TypeGenerics<'a>,
21}
22
23impl<'a> Constraints<'a> {
24 fn get(item: &'a syn::ItemStruct) -> Self {
25 let (impl_generics, type_generics, maybe_where_clause) = item.generics.split_for_impl();
26 let input_constraints = maybe_where_clause
27 .map(|w| w.predicates.iter())
28 .into_iter()
29 .flatten()
30 .collect();
31
32 Self {
33 input_constraints,
34 impl_generics,
35 type_generics,
36 }
37 }
38}
39
40fn get_extended_entry(e: Type) -> Result<TokenStream2, Error> {
41 let syn::Type::Path(typepath) = &e else {
42 return Err(Error::new_spanned(e, "Expected a path type"));
43 };
44 let Some(path_segment) = typepath.path.segments.first() else {
45 return Err(Error::new_spanned(&typepath.path, "Path has no segments"));
46 };
47 let ident = &path_segment.ident;
48 let arguments = &path_segment.arguments;
49 Ok(quote! { #ident :: #arguments })
50}
51
52fn generate_view_code(input: ItemStruct, root: bool) -> Result<TokenStream2, Error> {
53 for field in &input.fields {
55 if field.ident.is_none() {
56 return Err(Error::new_spanned(field, "All fields must be named."));
57 }
58 }
59
60 let Constraints {
61 input_constraints,
62 impl_generics,
63 type_generics,
64 } = Constraints::get(&input);
65
66 let attrs: StructAttrs = deluxe::parse_attributes(&input)
67 .map_err(|e| Error::new_spanned(&input, format!("Failed to parse attributes: {e}")))?;
68 let context = attrs.context.or_else(|| {
69 input.generics.type_params().next().map(|param| {
70 let ident = ¶m.ident;
71 parse_quote! { #ident }
72 })
73 }).ok_or_else(|| {
74 Error::new_spanned(
75 &input,
76 "Missing context: either add a generic type parameter or specify the context with #[view(context = YourContextType)]"
77 )
78 })?;
79
80 let struct_name = &input.ident;
81 let field_types: Vec<_> = input.fields.iter().map(|field| &field.ty).collect();
82
83 let mut name_quotes = Vec::new();
84 let mut rollback_quotes = Vec::new();
85 let mut flush_quotes = Vec::new();
86 let mut test_flush_quotes = Vec::new();
87 let mut clear_quotes = Vec::new();
88 let mut has_pending_changes_quotes = Vec::new();
89 let mut num_init_keys_quotes = Vec::new();
90 let mut pre_load_keys_quotes = Vec::new();
91 let mut post_load_keys_quotes = Vec::new();
92 let num_fields = input.fields.len();
93 for (idx, e) in input.fields.iter().enumerate() {
94 let name = e.ident.clone().unwrap();
95 let test_flush_ident = format_ident!("deleted{}", idx);
96 let g = get_extended_entry(e.ty.clone())?;
97 name_quotes.push(quote! { #name });
98 rollback_quotes.push(quote! { self.#name.rollback(); });
99 flush_quotes.push(quote! { let #test_flush_ident = self.#name.flush(batch)?; });
100 test_flush_quotes.push(quote! { #test_flush_ident });
101 clear_quotes.push(quote! { self.#name.clear(); });
102 has_pending_changes_quotes.push(quote! {
103 if self.#name.has_pending_changes().await {
104 return true;
105 }
106 });
107 num_init_keys_quotes.push(quote! { #g :: NUM_INIT_KEYS });
108
109 let derive_key_logic = if num_fields < 256 {
110 let idx_u8 = idx as u8;
111 quote! {
112 let __linera_reserved_index = #idx_u8;
113 let __linera_reserved_base_key = context.base_key().derive_tag_key(linera_views::views::MIN_VIEW_TAG, &__linera_reserved_index)?;
114 }
115 } else {
116 assert!(num_fields < 65536);
117 let idx_u16 = idx as u16;
118 quote! {
119 let __linera_reserved_index = #idx_u16;
120 let __linera_reserved_base_key = context.base_key().derive_tag_key(linera_views::views::MIN_VIEW_TAG, &__linera_reserved_index)?;
121 }
122 };
123
124 pre_load_keys_quotes.push(quote! {
125 #derive_key_logic
126 keys.extend(#g :: pre_load(&context.clone_with_base_key(__linera_reserved_base_key))?);
127 });
128 post_load_keys_quotes.push(quote! {
129 #derive_key_logic
130 let __linera_reserved_pos_next = __linera_reserved_pos + #g :: NUM_INIT_KEYS;
131 let #name = #g :: post_load(context.clone_with_base_key(__linera_reserved_base_key), &values[__linera_reserved_pos..__linera_reserved_pos_next])?;
132 __linera_reserved_pos = __linera_reserved_pos_next;
133 });
134 }
135
136 let first_name_quote = name_quotes.first().ok_or(Error::new_spanned(
137 &input,
138 "Struct must have at least one field",
139 ))?;
140
141 let load_metrics = if root && cfg!(feature = "metrics") {
142 quote! {
143 #[cfg(not(target_arch = "wasm32"))]
144 linera_views::metrics::increment_counter(
145 &linera_views::metrics::LOAD_VIEW_COUNTER,
146 stringify!(#struct_name),
147 &context.base_key().bytes,
148 );
149 #[cfg(not(target_arch = "wasm32"))]
150 use linera_views::metrics::prometheus_util::MeasureLatency as _;
151 let _latency = linera_views::metrics::LOAD_VIEW_LATENCY.measure_latency();
152 }
153 } else {
154 quote! {}
155 };
156
157 Ok(quote! {
158 impl #impl_generics linera_views::views::View for #struct_name #type_generics
159 where
160 #context: linera_views::context::Context,
161 #(#input_constraints,)*
162 #(#field_types: linera_views::views::View<Context = #context>,)*
163 {
164 const NUM_INIT_KEYS: usize = #(<#field_types as linera_views::views::View>::NUM_INIT_KEYS)+*;
165
166 type Context = #context;
167
168 fn context(&self) -> &#context {
169 self.#first_name_quote.context()
170 }
171
172 fn pre_load(context: &#context) -> Result<Vec<Vec<u8>>, linera_views::ViewError> {
173 use linera_views::context::Context as _;
174 let mut keys = Vec::new();
175 #(#pre_load_keys_quotes)*
176 Ok(keys)
177 }
178
179 fn post_load(context: #context, values: &[Option<Vec<u8>>]) -> Result<Self, linera_views::ViewError> {
180 use linera_views::context::Context as _;
181 let mut __linera_reserved_pos = 0;
182 #(#post_load_keys_quotes)*
183 Ok(Self {#(#name_quotes),*})
184 }
185
186 async fn load(context: #context) -> Result<Self, linera_views::ViewError> {
187 use linera_views::{context::Context as _, store::ReadableKeyValueStore as _};
188 #load_metrics
189 if Self::NUM_INIT_KEYS == 0 {
190 Self::post_load(context, &[])
191 } else {
192 let keys = Self::pre_load(&context)?;
193 let values = context.store().read_multi_values_bytes(keys).await?;
194 Self::post_load(context, &values)
195 }
196 }
197
198
199 fn rollback(&mut self) {
200 #(#rollback_quotes)*
201 }
202
203 async fn has_pending_changes(&self) -> bool {
204 #(#has_pending_changes_quotes)*
205 false
206 }
207
208 fn flush(&mut self, batch: &mut linera_views::batch::Batch) -> Result<bool, linera_views::ViewError> {
209 #(#flush_quotes)*
210 Ok( #(#test_flush_quotes)&&* )
211 }
212
213 fn clear(&mut self) {
214 #(#clear_quotes)*
215 }
216 }
217 })
218}
219
220fn generate_root_view_code(input: ItemStruct) -> TokenStream2 {
221 let Constraints {
222 input_constraints,
223 impl_generics,
224 type_generics,
225 } = Constraints::get(&input);
226 let struct_name = &input.ident;
227
228 let increment_counter = if cfg!(feature = "metrics") {
229 quote! {
230 #[cfg(not(target_arch = "wasm32"))]
231 linera_views::metrics::increment_counter(
232 &linera_views::metrics::SAVE_VIEW_COUNTER,
233 stringify!(#struct_name),
234 &self.context().base_key().bytes,
235 );
236 }
237 } else {
238 quote! {}
239 };
240
241 quote! {
242 impl #impl_generics linera_views::views::RootView for #struct_name #type_generics
243 where
244 #(#input_constraints,)*
245 Self: linera_views::views::View,
246 {
247 async fn save(&mut self) -> Result<(), linera_views::ViewError> {
248 use linera_views::{context::Context as _, batch::Batch, store::WritableKeyValueStore as _, views::View as _};
249 #increment_counter
250 let mut batch = Batch::new();
251 self.flush(&mut batch)?;
252 if !batch.is_empty() {
253 self.context().store().write_batch(batch).await?;
254 }
255 Ok(())
256 }
257 }
258 }
259}
260
261fn generate_hash_view_code(input: ItemStruct) -> Result<TokenStream2, Error> {
262 for field in &input.fields {
264 if field.ident.is_none() {
265 return Err(Error::new_spanned(field, "All fields must be named."));
266 }
267 }
268
269 let Constraints {
270 input_constraints,
271 impl_generics,
272 type_generics,
273 } = Constraints::get(&input);
274 let struct_name = &input.ident;
275
276 let field_types = input.fields.iter().map(|field| &field.ty);
277 let mut field_hashes_mut = Vec::new();
278 let mut field_hashes = Vec::new();
279 for e in &input.fields {
280 let name = e.ident.as_ref().unwrap();
281 field_hashes_mut.push(quote! { hasher.write_all(self.#name.hash_mut().await?.as_ref())?; });
282 field_hashes.push(quote! { hasher.write_all(self.#name.hash().await?.as_ref())?; });
283 }
284
285 Ok(quote! {
286 impl #impl_generics linera_views::views::HashableView for #struct_name #type_generics
287 where
288 #(#field_types: linera_views::views::HashableView,)*
289 #(#input_constraints,)*
290 Self: linera_views::views::View,
291 {
292 type Hasher = linera_views::sha3::Sha3_256;
293
294 async fn hash_mut(&mut self) -> Result<<Self::Hasher as linera_views::views::Hasher>::Output, linera_views::ViewError> {
295 use linera_views::views::Hasher as _;
296 use std::io::Write as _;
297 let mut hasher = Self::Hasher::default();
298 #(#field_hashes_mut)*
299 Ok(hasher.finalize())
300 }
301
302 async fn hash(&self) -> Result<<Self::Hasher as linera_views::views::Hasher>::Output, linera_views::ViewError> {
303 use linera_views::views::Hasher as _;
304 use std::io::Write as _;
305 let mut hasher = Self::Hasher::default();
306 #(#field_hashes)*
307 Ok(hasher.finalize())
308 }
309 }
310 })
311}
312
313fn generate_crypto_hash_code(input: ItemStruct) -> TokenStream2 {
314 let Constraints {
315 input_constraints,
316 impl_generics,
317 type_generics,
318 } = Constraints::get(&input);
319 let field_types = input.fields.iter().map(|field| &field.ty);
320 let struct_name = &input.ident;
321 let hash_type = syn::Ident::new(&format!("{struct_name}Hash"), Span::call_site());
322 quote! {
323 impl #impl_generics linera_views::views::CryptoHashView
324 for #struct_name #type_generics
325 where
326 #(#field_types: linera_views::views::HashableView,)*
327 #(#input_constraints,)*
328 Self: linera_views::views::View,
329 {
330 async fn crypto_hash(&self) -> Result<linera_base::crypto::CryptoHash, linera_views::ViewError> {
331 use linera_base::crypto::{BcsHashable, CryptoHash};
332 use linera_views::{
333 generic_array::GenericArray,
334 sha3::{digest::OutputSizeUser, Sha3_256},
335 views::HashableView as _,
336 };
337 #[derive(serde::Serialize, serde::Deserialize)]
338 struct #hash_type(GenericArray<u8, <Sha3_256 as OutputSizeUser>::OutputSize>);
339 impl<'de> BcsHashable<'de> for #hash_type {}
340 let hash = self.hash().await?;
341 Ok(CryptoHash::new(&#hash_type(hash)))
342 }
343
344 async fn crypto_hash_mut(&mut self) -> Result<linera_base::crypto::CryptoHash, linera_views::ViewError> {
345 use linera_base::crypto::{BcsHashable, CryptoHash};
346 use linera_views::{
347 generic_array::GenericArray,
348 sha3::{digest::OutputSizeUser, Sha3_256},
349 views::HashableView as _,
350 };
351 #[derive(serde::Serialize, serde::Deserialize)]
352 struct #hash_type(GenericArray<u8, <Sha3_256 as OutputSizeUser>::OutputSize>);
353 impl<'de> BcsHashable<'de> for #hash_type {}
354 let hash = self.hash_mut().await?;
355 Ok(CryptoHash::new(&#hash_type(hash)))
356 }
357 }
358 }
359}
360
361fn generate_clonable_view_code(input: ItemStruct) -> Result<TokenStream2, Error> {
362 for field in &input.fields {
364 if field.ident.is_none() {
365 return Err(Error::new_spanned(field, "All fields must be named."));
366 }
367 }
368
369 let Constraints {
370 input_constraints,
371 impl_generics,
372 type_generics,
373 } = Constraints::get(&input);
374 let struct_name = &input.ident;
375
376 let mut clone_constraints = vec![];
377 let mut clone_fields = vec![];
378
379 for field in &input.fields {
380 let name = &field.ident;
381 let ty = &field.ty;
382 clone_constraints.push(quote! { #ty: ClonableView });
383 clone_fields.push(quote! { #name: self.#name.clone_unchecked()? });
384 }
385
386 Ok(quote! {
387 impl #impl_generics linera_views::views::ClonableView for #struct_name #type_generics
388 where
389 #(#input_constraints,)*
390 #(#clone_constraints,)*
391 Self: linera_views::views::View,
392 {
393 fn clone_unchecked(&mut self) -> Result<Self, linera_views::ViewError> {
394 Ok(Self {
395 #(#clone_fields,)*
396 })
397 }
398 }
399 })
400}
401
402fn to_token_stream(input: Result<TokenStream2, Error>) -> TokenStream {
403 match input {
404 Ok(tokens) => tokens.into(),
405 Err(err) => err.to_compile_error().into(),
406 }
407}
408
409#[proc_macro_derive(View, attributes(view))]
410pub fn derive_view(input: TokenStream) -> TokenStream {
411 let input = parse_macro_input!(input as ItemStruct);
412 let input = generate_view_code(input, false);
413 to_token_stream(input)
414}
415
416fn derive_hash_view_token_stream2(input: ItemStruct) -> Result<TokenStream2, Error> {
417 let mut stream = generate_view_code(input.clone(), false)?;
418 stream.extend(generate_hash_view_code(input)?);
419 Ok(stream)
420}
421
422#[proc_macro_derive(HashableView, attributes(view))]
423pub fn derive_hash_view(input: TokenStream) -> TokenStream {
424 let input = parse_macro_input!(input as ItemStruct);
425
426 let stream = derive_hash_view_token_stream2(input);
427 to_token_stream(stream)
428}
429
430fn derive_root_view_token_stream2(input: ItemStruct) -> Result<TokenStream2, Error> {
431 let mut stream = generate_view_code(input.clone(), true)?;
432 stream.extend(generate_root_view_code(input));
433 Ok(stream)
434}
435
436#[proc_macro_derive(RootView, attributes(view))]
437pub fn derive_root_view(input: TokenStream) -> TokenStream {
438 let input = parse_macro_input!(input as ItemStruct);
439
440 let stream = derive_root_view_token_stream2(input);
441 to_token_stream(stream)
442}
443
444fn derive_crypto_hash_view_token_stream2(input: ItemStruct) -> Result<TokenStream2, Error> {
445 let mut stream = generate_view_code(input.clone(), false)?;
446 stream.extend(generate_hash_view_code(input.clone())?);
447 stream.extend(generate_crypto_hash_code(input));
448 Ok(stream)
449}
450
451#[proc_macro_derive(CryptoHashView, attributes(view))]
452pub fn derive_crypto_hash_view(input: TokenStream) -> TokenStream {
453 let input = parse_macro_input!(input as ItemStruct);
454
455 let stream = derive_crypto_hash_view_token_stream2(input);
456 to_token_stream(stream)
457}
458
459fn derive_crypto_hash_root_view_token_stream2(input: ItemStruct) -> Result<TokenStream2, Error> {
460 let mut stream = generate_view_code(input.clone(), true)?;
461 stream.extend(generate_root_view_code(input.clone()));
462 stream.extend(generate_hash_view_code(input.clone())?);
463 stream.extend(generate_crypto_hash_code(input));
464 Ok(stream)
465}
466
467#[proc_macro_derive(CryptoHashRootView, attributes(view))]
468pub fn derive_crypto_hash_root_view(input: TokenStream) -> TokenStream {
469 let input = parse_macro_input!(input as ItemStruct);
470
471 let stream = derive_crypto_hash_root_view_token_stream2(input);
472 to_token_stream(stream)
473}
474
475#[cfg(test)]
476fn derive_hashable_root_view_token_stream2(input: ItemStruct) -> Result<TokenStream2, Error> {
477 let mut stream = generate_view_code(input.clone(), true)?;
478 stream.extend(generate_root_view_code(input.clone()));
479 stream.extend(generate_hash_view_code(input)?);
480 Ok(stream)
481}
482
483#[proc_macro_derive(HashableRootView, attributes(view))]
484#[cfg(test)]
485pub fn derive_hashable_root_view(input: TokenStream) -> TokenStream {
486 let input = parse_macro_input!(input as ItemStruct);
487
488 let stream = derive_hashable_root_view_token_stream2(input);
489 to_token_stream(stream)
490}
491
492#[proc_macro_derive(ClonableView, attributes(view))]
493pub fn derive_clonable_view(input: TokenStream) -> TokenStream {
494 let input = parse_macro_input!(input as ItemStruct);
495 match generate_clonable_view_code(input) {
496 Ok(tokens) => tokens.into(),
497 Err(err) => err.to_compile_error().into(),
498 }
499}
500
501#[cfg(test)]
502pub mod tests {
503
504 use quote::quote;
505 use syn::{parse_quote, AngleBracketedGenericArguments};
506
507 use crate::*;
508
509 fn pretty(tokens: TokenStream2) -> String {
510 prettyplease::unparse(
511 &syn::parse2::<syn::File>(tokens).expect("failed to parse test output"),
512 )
513 }
514
515 #[test]
516 fn test_generate_view_code() {
517 for context in SpecificContextInfo::test_cases() {
518 let input = context.test_view_input();
519 insta::assert_snapshot!(
520 format!(
521 "test_generate_view_code{}_{}",
522 if cfg!(feature = "metrics") {
523 "_metrics"
524 } else {
525 ""
526 },
527 context.name,
528 ),
529 pretty(generate_view_code(input, true).unwrap())
530 );
531 }
532 }
533
534 #[test]
535 fn test_generate_hash_view_code() {
536 for context in SpecificContextInfo::test_cases() {
537 let input = context.test_view_input();
538 insta::assert_snapshot!(
539 format!("test_generate_hash_view_code_{}", context.name),
540 pretty(generate_hash_view_code(input).unwrap())
541 );
542 }
543 }
544
545 #[test]
546 fn test_generate_root_view_code() {
547 for context in SpecificContextInfo::test_cases() {
548 let input = context.test_view_input();
549 insta::assert_snapshot!(
550 format!(
551 "test_generate_root_view_code{}_{}",
552 if cfg!(feature = "metrics") {
553 "_metrics"
554 } else {
555 ""
556 },
557 context.name,
558 ),
559 pretty(generate_root_view_code(input))
560 );
561 }
562 }
563
564 #[test]
565 fn test_generate_crypto_hash_code() {
566 for context in SpecificContextInfo::test_cases() {
567 let input = context.test_view_input();
568 insta::assert_snapshot!(pretty(generate_crypto_hash_code(input)));
569 }
570 }
571
572 #[test]
573 fn test_generate_clonable_view_code() {
574 for context in SpecificContextInfo::test_cases() {
575 let input = context.test_view_input();
576 insta::assert_snapshot!(pretty(generate_clonable_view_code(input).unwrap()));
577 }
578 }
579
580 #[derive(Clone)]
581 pub struct SpecificContextInfo {
582 name: String,
583 attribute: Option<TokenStream2>,
584 context: Type,
585 generics: AngleBracketedGenericArguments,
586 where_clause: Option<TokenStream2>,
587 }
588
589 impl SpecificContextInfo {
590 pub fn empty() -> Self {
591 SpecificContextInfo {
592 name: "C".to_string(),
593 attribute: None,
594 context: syn::parse_quote! { C },
595 generics: syn::parse_quote! { <C> },
596 where_clause: None,
597 }
598 }
599
600 pub fn new(context: syn::Type) -> Self {
601 let name = quote! { #context };
602 SpecificContextInfo {
603 name: format!("{name}")
604 .replace(' ', "")
605 .replace([':', '<', '>'], "_"),
606 attribute: Some(quote! { #[view(context = #context)] }),
607 context,
608 generics: parse_quote! { <> },
609 where_clause: None,
610 }
611 }
612
613 pub fn with_dummy_where_clause(mut self) -> Self {
618 self.generics.args.push(parse_quote! { MyParam });
619 self.where_clause = Some(quote! {
620 where MyParam: Send + Sync + 'static,
621 });
622 self.name.push_str("_with_where");
623
624 self
625 }
626
627 pub fn test_cases() -> impl Iterator<Item = Self> {
628 Some(Self::empty())
629 .into_iter()
630 .chain(
631 [
632 syn::parse_quote! { CustomContext },
633 syn::parse_quote! { custom::path::to::ContextType },
634 syn::parse_quote! { custom::GenericContext<T> },
635 ]
636 .into_iter()
637 .map(Self::new),
638 )
639 .flat_map(|case| [case.clone(), case.with_dummy_where_clause()])
640 }
641
642 pub fn test_view_input(&self) -> ItemStruct {
643 let SpecificContextInfo {
644 attribute,
645 context,
646 generics,
647 where_clause,
648 ..
649 } = self;
650
651 parse_quote! {
652 #attribute
653 struct TestView #generics
654 #where_clause
655 {
656 register: RegisterView<#context, usize>,
657 collection: CollectionView<#context, usize, RegisterView<#context, usize>>,
658 }
659 }
660 }
661 }
662
663 #[test]
665 fn test_tuple_struct_failure() {
666 let input: ItemStruct = parse_quote! {
667 struct TestView<C>(RegisterView<C, u64>);
668 };
669 let result = generate_view_code(input, false);
670 assert!(result.is_err());
671 let error_msg = result.unwrap_err().to_string();
672 assert!(error_msg.contains("All fields must be named"));
673 }
674
675 #[test]
676 fn test_empty_struct_failure() {
677 let input: ItemStruct = parse_quote! {
678 struct TestView<C> {}
679 };
680 let result = generate_view_code(input, false);
681 assert!(result.is_err());
682 let error_msg = result.unwrap_err().to_string();
683 assert!(error_msg.contains("Struct must have at least one field"));
684 }
685
686 #[test]
687 fn test_missing_context_no_generics_failure() {
688 let input: ItemStruct = parse_quote! {
689 struct TestView {
690 register: RegisterView<CustomContext, u64>,
691 }
692 };
693 let result = generate_view_code(input, false);
694 assert!(result.is_err());
695 let error_msg = result.unwrap_err().to_string();
696 assert!(error_msg.contains("Missing context"));
697 }
698
699 #[test]
700 fn test_missing_context_empty_generics_failure() {
701 let input: ItemStruct = parse_quote! {
702 struct TestView<> {
703 register: RegisterView<CustomContext, u64>,
704 }
705 };
706 let result = generate_view_code(input, false);
707 assert!(result.is_err());
708 let error_msg = result.unwrap_err().to_string();
709 assert!(error_msg.contains("Missing context"));
710 }
711
712 #[test]
713 fn test_non_path_type_failure() {
714 let input: ItemStruct = parse_quote! {
715 struct TestView<C> {
716 field: fn() -> i32,
717 }
718 };
719 let result = generate_view_code(input, false);
720 assert!(result.is_err());
721 let error_msg = result.unwrap_err().to_string();
722 assert!(error_msg.contains("Expected a path type"));
723 }
724
725 #[test]
726 fn test_unnamed_field_in_hash_view_failure() {
727 let input: ItemStruct = parse_quote! {
728 struct TestView<C>(RegisterView<C, u64>);
729 };
730 let result = generate_hash_view_code(input);
731 assert!(result.is_err());
732 let error_msg = result.unwrap_err().to_string();
733 assert!(error_msg.contains("All fields must be named"));
734 }
735
736 #[test]
737 fn test_unnamed_field_in_clonable_view_failure() {
738 let input: ItemStruct = parse_quote! {
739 struct TestView<C>(RegisterView<C, u64>);
740 };
741 let result = generate_clonable_view_code(input);
742 assert!(result.is_err());
743 let error_msg = result.unwrap_err().to_string();
744 assert!(error_msg.contains("All fields must be named"));
745 }
746
747 #[test]
748 fn test_array_type_failure() {
749 let input: ItemStruct = parse_quote! {
750 struct TestView<C> {
751 field: [u8; 32],
752 }
753 };
754 let result = generate_view_code(input, false);
755 assert!(result.is_err());
756 let error_msg = result.unwrap_err().to_string();
757 assert!(error_msg.contains("Expected a path type"));
758 }
759
760 #[test]
761 fn test_reference_type_failure() {
762 let input: ItemStruct = parse_quote! {
763 struct TestView<C> {
764 field: &'static str,
765 }
766 };
767 let result = generate_view_code(input, false);
768 assert!(result.is_err());
769 let error_msg = result.unwrap_err().to_string();
770 assert!(error_msg.contains("Expected a path type"));
771 }
772
773 #[test]
774 fn test_pointer_type_failure() {
775 let input: ItemStruct = parse_quote! {
776 struct TestView<C> {
777 field: *const i32,
778 }
779 };
780 let result = generate_view_code(input, false);
781 assert!(result.is_err());
782 let error_msg = result.unwrap_err().to_string();
783 assert!(error_msg.contains("Expected a path type"));
784 }
785
786 #[test]
787 fn test_generate_root_view_code_with_empty_struct() {
788 let input: ItemStruct = parse_quote! {
789 struct TestView<C> {}
790 };
791 let result = generate_view_code(input.clone(), true);
793 assert!(result.is_err());
794 let error_msg = result.unwrap_err().to_string();
795 assert!(error_msg.contains("Struct must have at least one field"));
796 }
797
798 #[test]
799 fn test_generate_functions_behavior_differences() {
800 let input: ItemStruct = parse_quote! {
802 struct TestView<C> {
803 field: fn() -> i32,
804 }
805 };
806
807 let view_result = generate_view_code(input.clone(), false);
809 assert!(view_result.is_err());
810 let error_msg = view_result.unwrap_err().to_string();
811 assert!(error_msg.contains("Expected a path type"));
812
813 let hash_result = generate_hash_view_code(input.clone());
815 assert!(hash_result.is_ok());
816
817 let _result = generate_crypto_hash_code(input);
819 }
820
821 #[test]
822 fn test_crypto_hash_code_generation_failure() {
823 let input: ItemStruct = parse_quote! {
825 struct TestView<C> {
826 register: RegisterView<C, usize>,
827 }
828 };
829 let _result = generate_crypto_hash_code(input);
830 }
831}