diff --git a/config/base/derive/src/lib.rs b/config/base/derive/src/lib.rs index 285119ea6e9..0d8dd02c7e8 100644 --- a/config/base/derive/src/lib.rs +++ b/config/base/derive/src/lib.rs @@ -622,11 +622,15 @@ pub fn view(input: TokenStream) -> TokenStream { let view = gen_view_struct(ast); let impl_from = gen_impl_from(&original, &view); let impl_default = gen_impl_default(&original, &view); + let impl_has_view = gen_impl_has_view(&original); + let assertions = gen_assertions(&view); let out = quote! { #original + #impl_has_view #view #impl_from #impl_default + #assertions }; out.into() } @@ -741,6 +745,39 @@ fn gen_impl_default(original: &ViewInput, view: &ViewInput) -> proc_macro2::Toke } } +fn gen_impl_has_view(original: &ViewInput) -> proc_macro2::TokenStream { + let ViewInput { + generics, + ident: view_ident, + .. + } = original; + let (impl_generics, ty_generics, where_clause) = generics.split_for_impl(); + + quote! { + impl #impl_generics iroha_config_base::view::HasView for #view_ident #ty_generics #where_clause {} + } +} + +fn gen_assertions(view: &ViewInput) -> proc_macro2::TokenStream { + let ViewInput { fields, .. } = view; + let field_types = extract_field_types(fields); + let messages: Vec = extract_field_idents(fields) + .iter() + .map(|ident| { + format!("Field `{ident}` has it's own view, consider adding attribute #[view(into = ViewType)]") + }) + .collect(); + quote! { + /// Assert that every field of 'View' doesn't implement `HasView` trait + const _: () = { + use iroha_config_base::view::NoView; + #( + const _: () = assert!(!iroha_config_base::view::IsHasView::<#field_types>::IS_HAS_VIEW, #messages); + )* + }; + } +} + /// Change [`Field`] type to `Type` if `#[view(type = Type)]` is present fn view_field_change_type(field: &mut Field) { if let Some(ty) = field @@ -791,3 +828,8 @@ fn extract_field_idents(fields: &[Field]) -> Vec<&Ident> { }) .collect::>() } + +/// Return [`Vec`] of fields types +fn extract_field_types(fields: &[Field]) -> Vec<&Type> { + fields.iter().map(|field| &field.ty).collect::>() +} diff --git a/config/base/src/lib.rs b/config/base/src/lib.rs index f8af1816fd3..3336a2c400d 100644 --- a/config/base/src/lib.rs +++ b/config/base/src/lib.rs @@ -232,3 +232,27 @@ pub trait Configurable: Serialize + DeserializeOwned { /// Get inner documentation for non-leaf fields fn get_inner_docs() -> String; } + +pub mod view { + //! Module for view related traits and structs + + /// Marker trait to set default value `IS_HAS_VIEW` to `false` + pub trait NoView { + /// [`Self`] doesn't implement [`HasView`] + const IS_HAS_VIEW: bool = false; + } + impl NoView for T {} + + /// Marker traits for types for which views are implemented + pub trait HasView {} + + /// Wrapper structure used to check if type implements `[HasView]` + /// If `T` doesn't implement [`HasView`] then `NoView::IS_HAS_VIEW` (`false`) will be used + /// Otherwise `IsHasView::IS_HAS_VIEW` (`true`) from `impl` block will shadow `NoView::IS_HAS_VIEW` + pub struct IsHasView(std::marker::PhantomData); + + impl IsHasView { + /// `T` implements trait [`HasView`] + pub const IS_HAS_VIEW: bool = true; + } +}